AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/10/29 04:41 flow patch-triage
9m Args:
null

Results:
{
  "EnableConfigs": [
    "VSOCKETS",
    "VSOCKETS_LOOPBACK",
    "VIRTIO_VSOCKETS",
    "VHOST_VSOCK"
  ],
  "FocusSymbols": [
    "vsock_dgram_get_transport",
    "vsock_bind_common",
    "virtio_transport_dgram_enqueue",
    "virtio_transport_dgram_dequeue",
    "virtio_transport_recv_pkt",
    "vhost_transport_do_send_pkt",
    "vsock_dgram_sendmsg",
    "vsock_dgram_connect"
  ],
  "KMSANReasoning": "The patch series implements SOCK_DGRAM support for virtio-vsock and vhost-vsock, adding datagram enqueue/dequeue, transport selection, loopback and peer routing, and ioctl/poll handling.\n\nKMSAN vs. KASAN/standard detectors evaluation:\n1. Potential bugs introduced or touched by this patch series primarily concern socket buffer lifecycle management (skb allocation, trimming, freeing via kfree_skb/sock_wfree), module and socket reference counting (try_module_get/module_put, sock_put), locking synchronization (vsock_register_mutex, vsock_table_lock, vvs-\u003etx_lock, socket locks), and bounds/truncation checks. These risks are covered by standard bug detectors, KASAN, and LOCKDEP.\n2. Information leaks / copy-to-user inspection:\n   - In `virtio_transport_dgram_dequeue()`, user addresses are constructed using `vsock_addr_init()`, which zeroes the entire `struct sockaddr_vm` (including structure padding) via `memset` prior to initializing field values.\n   - Datagram payloads copied to user space via `skb_copy_datagram_iter()` are bounded by `payload_len`, which is strictly validated against the received descriptor length `len` and trimmed with `pskb_trim_unique()`.\n   - The ioctl handler for SIOCINQ reports `n_bytes`, a cleanly initialized scalar integer.\n3. Uninitialized state inspection:\n   - `struct virtio_vsock_skb_cb` fields (`to_host`, `tx_accounted`) are stored in `skb-\u003ecb`, which is zeroed on skb allocation (`skbuff_clear()`), and are explicitly assigned before being queried in control flow or copied into socket structures.\n   - In `virtio_vsock_rx_fill()`, the scatterlist array `vsock-\u003ein_bufs` is initialized with `sg_init_table()` prior to descriptor submission.\n\nBecause all newly introduced structures, variables, and buffer copies are properly zeroed or initialized, and any memory-safety risks are detectable by KASAN and LOCKDEP, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch series adds SOCK_DGRAM datagram support to virtio-vsock, vhost-vsock, and vsock loopback transports. It introduces new datagram transport selection logic (vsock_dgram_get_transport), refactors port binding (vsock_bind_common), implements datagram queuing and dequeuing (virtio_transport_dgram_enqueue, virtio_transport_dgram_dequeue), handles datagram packet receipt (virtio_transport_recv_pkt), and modifies vhost descriptor processing (vhost_transport_do_send_pkt). AF_VSOCK and virtio/vhost devices are standard networking facilities accessible in virtualized environments, making these changes well worth fuzzing.",
  "WorthFuzzing": true
}

1/1 2026/10/29 04:41 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 827c224662592a09246125151432c72c4a814d76\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Oct 29 04:41:17 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c\nindex 0cb87971d74919..2294ed8d30a84b 100644\n--- a/drivers/vhost/vsock.c\n+++ b/drivers/vhost/vsock.c\n@@ -32,7 +32,8 @@\n static const int vhost_vsock_bits[] = {\n \tVHOST_FEATURES,\n \tVIRTIO_F_ACCESS_PLATFORM,\n-\tVIRTIO_VSOCK_F_SEQPACKET\n+\tVIRTIO_VSOCK_F_SEQPACKET,\n+\tVIRTIO_VSOCK_F_DGRAM,\n };\n \n #define VHOST_VSOCK_FEATURES VHOST_FEATURES_U64(vhost_vsock_bits, 0)\n@@ -62,6 +63,7 @@ struct vhost_vsock {\n \tu32 guest_cid;\n \tbool seqpacket_allow;\n \tbool ever_started; /* set on first SET_RUNNING(1); never cleared */\n+\tbool dgram_allow;\n };\n \n static u32 vhost_transport_get_local_cid(void)\n@@ -183,6 +185,14 @@ vhost_transport_do_send_pkt(struct vhost_vsock *vsock,\n \t\t * buffer, we split it using multiple buffers.\n \t\t */\n \t\tif (payload_len \u003e iov_len - sizeof(*hdr)) {\n+\t\t\t/* Datagrams must fit in a single descriptor chain. */\n+\t\t\tif (le16_to_cpu(hdr-\u003etype) == VIRTIO_VSOCK_TYPE_DGRAM) {\n+\t\t\t\tkfree_skb(skb);\n+\t\t\t\tvhost_add_used(vq, head, 0);\n+\t\t\t\tadded = true;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n \t\t\tpayload_len = iov_len - sizeof(*hdr);\n \n \t\t\t/* As we are copying pieces of large packet's buffer to\n@@ -436,6 +446,8 @@ static bool vhost_transport_msgzerocopy_allow(void)\n \n static bool vhost_transport_seqpacket_allow(struct vsock_sock *vsk,\n \t\t\t\t\t    u32 remote_cid);\n+static bool vhost_transport_dgram_allow(struct vsock_sock *vsk,\n+\t\t\t\t\tu32 cid, u32 port);\n \n static bool\n vhost_transport_stream_allow(struct vsock_sock *vsk, u32 cid, u32 port)\n@@ -460,7 +472,8 @@ static struct virtio_transport vhost_transport = {\n \t\t.dgram_enqueue            = virtio_transport_dgram_enqueue,\n \t\t.dgram_dequeue            = virtio_transport_dgram_dequeue,\n \t\t.dgram_bind               = virtio_transport_dgram_bind,\n-\t\t.dgram_allow              = virtio_transport_dgram_allow,\n+\t\t.dgram_allow              = vhost_transport_dgram_allow,\n+\t\t.dgram_transport_match    = virtio_transport_dgram_transport_match,\n \n \t\t.stream_enqueue           = virtio_transport_stream_enqueue,\n \t\t.stream_dequeue           = virtio_transport_stream_dequeue,\n@@ -516,6 +529,24 @@ static bool vhost_transport_seqpacket_allow(struct vsock_sock *vsk,\n \treturn seqpacket_allow;\n }\n \n+static bool vhost_transport_dgram_allow(struct vsock_sock *vsk,\n+\t\t\t\t\tu32 cid, u32 port)\n+{\n+\tstruct net *net = sock_net(sk_vsock(vsk));\n+\tstruct vhost_vsock *vsock;\n+\tbool dgram_allow = false;\n+\n+\trcu_read_lock();\n+\tvsock = vhost_vsock_get(cid, net);\n+\n+\tif (vsock)\n+\t\tdgram_allow = vsock-\u003edgram_allow;\n+\n+\trcu_read_unlock();\n+\n+\treturn dgram_allow;\n+}\n+\n static void vhost_vsock_handle_tx_kick(struct vhost_work *work)\n {\n \tstruct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue,\n@@ -737,6 +768,7 @@ static int vhost_vsock_dev_open(struct inode *inode, struct file *file)\n \tvsock-\u003eguest_cid = 0; /* no CID assigned yet */\n \tvsock-\u003eseqpacket_allow = false;\n \tvsock-\u003eever_started = false;\n+\tvsock-\u003edgram_allow = false;\n \n \tatomic_set(\u0026vsock-\u003equeued_replies, 0);\n \n@@ -890,6 +922,7 @@ static int vhost_vsock_set_features(struct vhost_vsock *vsock, u64 features)\n \t}\n \n \tvsock-\u003eseqpacket_allow = features \u0026 (1ULL \u003c\u003c VIRTIO_VSOCK_F_SEQPACKET);\n+\tvsock-\u003edgram_allow = features \u0026 (1ULL \u003c\u003c VIRTIO_VSOCK_F_DGRAM);\n \n \tfor (i = 0; i \u003c ARRAY_SIZE(vsock-\u003evqs); i++) {\n \t\tvq = \u0026vsock-\u003evqs[i];\ndiff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h\nindex f91704731057ed..91945031fcc535 100644\n--- a/include/linux/virtio_vsock.h\n+++ b/include/linux/virtio_vsock.h\n@@ -12,6 +12,9 @@\n struct virtio_vsock_skb_cb {\n \tbool reply;\n \tbool tap_delivered;\n+\t/* Datagram payload is still charged to bytes_unsent. */\n+\tbool tx_accounted;\n+\tbool to_host;\n \tu32 offset;\n };\n \n@@ -171,6 +174,7 @@ struct virtio_vsock_sock {\n \n struct virtio_vsock_pkt_info {\n \tu32 remote_cid, remote_port;\n+\tconst struct virtio_transport *t_ops;\n \tstruct vsock_sock *vsk;\n \tstruct msghdr *msg;\n \tstruct net *net;\n@@ -261,6 +265,7 @@ bool virtio_transport_stream_allow(struct vsock_sock *vsk, u32 cid, u32 port);\n int virtio_transport_dgram_bind(struct vsock_sock *vsk,\n \t\t\t\tstruct sockaddr_vm *addr);\n bool virtio_transport_dgram_allow(struct vsock_sock *vsk, u32 cid, u32 port);\n+bool virtio_transport_dgram_transport_match(const struct vsock_transport *t);\n \n int virtio_transport_connect(struct vsock_sock *vsk);\n \n@@ -274,6 +279,7 @@ virtio_transport_stream_enqueue(struct vsock_sock *vsk,\n \t\t\t\tsize_t len);\n int\n virtio_transport_dgram_enqueue(struct vsock_sock *vsk,\n+\t\t\t       const struct vsock_transport *transport,\n \t\t\t       struct sockaddr_vm *remote_addr,\n \t\t\t       struct msghdr *msg,\n \t\t\t       size_t len);\ndiff --git a/include/net/af_vsock.h b/include/net/af_vsock.h\nindex e8481f12312e91..f9bcd703a7768a 100644\n--- a/include/net/af_vsock.h\n+++ b/include/net/af_vsock.h\n@@ -121,9 +121,15 @@ struct vsock_transport {\n \tint (*dgram_bind)(struct vsock_sock *, struct sockaddr_vm *);\n \tint (*dgram_dequeue)(struct vsock_sock *vsk, struct msghdr *msg,\n \t\t\t     size_t len, int flags);\n-\tint (*dgram_enqueue)(struct vsock_sock *, struct sockaddr_vm *,\n-\t\t\t     struct msghdr *, size_t len);\n+\tint (*dgram_enqueue)(struct vsock_sock *vsk,\n+\t\t\t     const struct vsock_transport *transport,\n+\t\t\t     struct sockaddr_vm *remote_addr,\n+\t\t\t     struct msghdr *msg, size_t len);\n \tbool (*dgram_allow)(struct vsock_sock *vsk, u32 cid, u32 port);\n+\t/* Optional: another transport can use this socket's datagram state. */\n+\tbool (*dgram_transport_match)(const struct vsock_transport *transport);\n+\t/* Optional payload length override, called with the receive queue locked. */\n+\tsize_t (*dgram_payload_len)(const struct sk_buff *skb);\n \n \t/* STREAM. */\n \t/* TODO: stream_bind() */\n@@ -220,6 +226,7 @@ void vsock_remove_pending(struct sock *listener, struct sock *pending);\n void vsock_enqueue_accept(struct sock *listener, struct sock *connected);\n void vsock_pending_to_accept(struct sock *listener, struct sock *pending);\n void vsock_insert_connected(struct vsock_sock *vsk);\n+int vsock_bind_common(struct vsock_sock *vsk, struct sockaddr_vm *addr);\n void vsock_remove_bound(struct vsock_sock *vsk);\n void vsock_remove_connected(struct vsock_sock *vsk);\n struct sock *vsock_find_bound_socket(struct sockaddr_vm *addr);\n@@ -237,6 +244,9 @@ void vsock_remove_sock(struct vsock_sock *vsk);\n void vsock_for_each_connected_socket(struct vsock_transport *transport,\n \t\t\t\t     void (*fn)(struct sock *sk));\n int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk);\n+const struct vsock_transport *\n+vsock_dgram_get_transport(struct vsock_sock *vsk,\n+\t\t\t  struct sockaddr_vm *remote_addr);\n bool vsock_find_cid(unsigned int cid);\n void vsock_linger(struct sock *sk);\n \ndiff --git a/include/uapi/linux/virtio_vsock.h b/include/uapi/linux/virtio_vsock.h\nindex 64738838bee573..27b4b2b8bf13b9 100644\n--- a/include/uapi/linux/virtio_vsock.h\n+++ b/include/uapi/linux/virtio_vsock.h\n@@ -40,6 +40,7 @@\n \n /* The feature bitmap for virtio vsock */\n #define VIRTIO_VSOCK_F_SEQPACKET\t1\t/* SOCK_SEQPACKET supported */\n+#define VIRTIO_VSOCK_F_DGRAM\t\t3\t/* SOCK_DGRAM supported */\n \n struct virtio_vsock_config {\n \t__le64 guest_cid;\n@@ -69,6 +70,7 @@ struct virtio_vsock_hdr {\n enum virtio_vsock_type {\n \tVIRTIO_VSOCK_TYPE_STREAM = 1,\n \tVIRTIO_VSOCK_TYPE_SEQPACKET = 2,\n+\tVIRTIO_VSOCK_TYPE_DGRAM = 3,\n };\n \n enum virtio_vsock_op {\ndiff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c\nindex d0508887a2fbac..6419498a6fe276 100644\n--- a/net/vmw_vsock/af_vsock.c\n+++ b/net/vmw_vsock/af_vsock.c\n@@ -136,6 +136,7 @@\n #include \u003clinux/types.h\u003e\n #include \u003clinux/bitops.h\u003e\n #include \u003clinux/cred.h\u003e\n+#include \u003clinux/err.h\u003e\n #include \u003clinux/errqueue.h\u003e\n #include \u003clinux/init.h\u003e\n #include \u003clinux/io.h\u003e\n@@ -599,6 +600,7 @@ static void vsock_deassign_transport(struct vsock_sock *vsk)\n int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)\n {\n \tconst struct vsock_transport *new_transport;\n+\tconst struct vsock_transport *fallback_transport = NULL;\n \tstruct sock *sk = sk_vsock(vsk);\n \tunsigned int remote_cid = vsk-\u003eremote_addr.svm_cid;\n \t__u8 remote_flags;\n@@ -622,7 +624,12 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)\n \n \tswitch (sk-\u003esk_type) {\n \tcase SOCK_DGRAM:\n-\t\tnew_transport = transport_dgram;\n+\t\tif (transport_dgram)\n+\t\t\tnew_transport = transport_dgram;\n+\t\telse if (transport_g2h)\n+\t\t\tnew_transport = transport_g2h;\n+\t\telse\n+\t\t\tnew_transport = transport_h2g;\n \t\tbreak;\n \tcase SOCK_STREAM:\n \tcase SOCK_SEQPACKET:\n@@ -661,6 +668,11 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)\n \t\tret = -ENODEV;\n \t\tgoto err;\n \t}\n+\tif (sk-\u003esk_type == SOCK_DGRAM \u0026\u0026 !transport_dgram \u0026\u0026\n+\t    new_transport == transport_g2h \u0026\u0026 transport_h2g \u0026\u0026\n+\t    transport_h2g != new_transport \u0026\u0026\n+\t    try_module_get(transport_h2g-\u003emodule))\n+\t\tfallback_transport = transport_h2g;\n \n \t/* It's safe to release the mutex after a successful try_module_get().\n \t * Whichever transport `new_transport` points at, it won't go away until\n@@ -697,6 +709,14 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)\n \t}\n \n \tret = new_transport-\u003einit(vsk, psk);\n+\tif (ret == -ESOCKTNOSUPPORT \u0026\u0026 fallback_transport) {\n+\t\tmodule_put(new_transport-\u003emodule);\n+\t\tnew_transport = fallback_transport;\n+\t\tfallback_transport = NULL;\n+\t\tret = new_transport-\u003einit(vsk, psk);\n+\t}\n+\tif (fallback_transport)\n+\t\tmodule_put(fallback_transport-\u003emodule);\n \tif (ret) {\n \t\tmodule_put(new_transport-\u003emodule);\n \t\treturn ret;\n@@ -711,6 +731,98 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)\n }\n EXPORT_SYMBOL_GPL(vsock_assign_transport);\n \n+/**\n+ * vsock_dgram_get_transport - select a transport for a datagram destination\n+ * @vsk: socket whose datagram state will be used\n+ * @remote_addr: destination address\n+ *\n+ * The caller must hold the socket lock and release the returned transport's\n+ * module reference after use. Dedicated datagram transports retain their\n+ * original selection; compatible transports share the socket's existing state.\n+ * Parent fallback sets VMADDR_FLAG_TO_HOST to preserve that route on connect.\n+ *\n+ * Return: a referenced transport or an ERR_PTR on failure.\n+ */\n+const struct vsock_transport *\n+vsock_dgram_get_transport(struct vsock_sock *vsk,\n+\t\t\t  struct sockaddr_vm *remote_addr)\n+{\n+\tconst struct vsock_transport *owner = vsk-\u003etransport;\n+\tconst struct vsock_transport *transport;\n+\tstruct sock *sk = sk_vsock(vsk);\n+\tu32 cid = remote_addr-\u003esvm_cid;\n+\tu32 local_cid;\n+\tbool to_host = false;\n+\tint err = -ENODEV;\n+\n+\tif (!owner)\n+\t\treturn ERR_PTR(-ENODEV);\n+\n+\tmutex_lock(\u0026vsock_register_mutex);\n+\tif (!owner-\u003edgram_transport_match) {\n+\t\ttransport = owner;\n+\t} else if (vsock_use_local_transport(cid)) {\n+\t\ttransport = transport_local;\n+\t} else if (cid \u003c= VMADDR_CID_HOST ||\n+\t\t   (remote_addr-\u003esvm_flags \u0026 VMADDR_FLAG_TO_HOST)) {\n+\t\ttransport = transport_g2h;\n+\t} else if (transport_h2g \u0026\u0026\n+\t\t   (!transport_h2g-\u003ehas_remote_cid ||\n+\t\t    transport_h2g-\u003ehas_remote_cid(vsk, cid))) {\n+\t\ttransport = transport_h2g;\n+\t} else if (sock_net(sk)-\u003evsock.g2h_fallback \u0026\u0026 transport_g2h \u0026\u0026\n+\t\t   transport_g2h-\u003ehas_remote_cid \u0026\u0026\n+\t\t   transport_g2h-\u003ehas_remote_cid(vsk, cid)) {\n+\t\ttransport = transport_g2h;\n+\t\tto_host = true;\n+\t} else {\n+\t\ttransport = transport_h2g;\n+\t}\n+\n+\tif (!transport)\n+\t\tgoto out;\n+\tif (owner-\u003edgram_transport_match \u0026\u0026\n+\t    !owner-\u003edgram_transport_match(transport)) {\n+\t\terr = -ESOCKTNOSUPPORT;\n+\t\tgoto out;\n+\t}\n+\t/* Loopback source addresses must route replies back to loopback. */\n+\tif (owner-\u003edgram_transport_match \u0026\u0026 transport == transport_local \u0026\u0026\n+\t    vsk-\u003elocal_addr.svm_cid != VMADDR_CID_ANY \u0026\u0026\n+\t    !vsock_use_local_transport(vsk-\u003elocal_addr.svm_cid)) {\n+\t\terr = -EADDRNOTAVAIL;\n+\t\tgoto out;\n+\t}\n+\tif (!try_module_get(transport-\u003emodule))\n+\t\tgoto out;\n+\tmutex_unlock(\u0026vsock_register_mutex);\n+\n+\tif (owner-\u003edgram_transport_match) {\n+\t\tlocal_cid = transport-\u003eget_local_cid();\n+\t\tif (vsk-\u003elocal_addr.svm_cid != VMADDR_CID_ANY \u0026\u0026\n+\t\t    local_cid != VMADDR_CID_LOCAL \u0026\u0026\n+\t\t    vsk-\u003elocal_addr.svm_cid != local_cid) {\n+\t\t\terr = -EADDRNOTAVAIL;\n+\t\t\tgoto put_transport;\n+\t\t}\n+\t}\n+\tif (!transport-\u003edgram_allow(vsk, cid, remote_addr-\u003esvm_port)) {\n+\t\terr = -EINVAL;\n+\t\tgoto put_transport;\n+\t}\n+\tif (to_host)\n+\t\tremote_addr-\u003esvm_flags |= VMADDR_FLAG_TO_HOST;\n+\treturn transport;\n+\n+put_transport:\n+\tmodule_put(transport-\u003emodule);\n+\treturn ERR_PTR(err);\n+out:\n+\tmutex_unlock(\u0026vsock_register_mutex);\n+\treturn ERR_PTR(err);\n+}\n+EXPORT_SYMBOL_GPL(vsock_dgram_get_transport);\n+\n /*\n  * Provide safe access to static transport_{h2g,g2h,dgram,local} callbacks.\n  * Otherwise we may race with module removal. Do not use on `vsk-\u003etransport`.\n@@ -832,8 +944,8 @@ static void vsock_pending_work(struct work_struct *work)\n \n /**** SOCKET OPERATIONS ****/\n \n-static int __vsock_bind_connectible(struct vsock_sock *vsk,\n-\t\t\t\t    struct sockaddr_vm *addr)\n+static int __vsock_bind_common(struct vsock_sock *vsk,\n+\t\t\t       struct sockaddr_vm *addr)\n {\n \tstruct net *net = sock_net(sk_vsock(vsk));\n \tstruct sockaddr_vm new_addr;\n@@ -888,11 +1000,26 @@ static int __vsock_bind_connectible(struct vsock_sock *vsk,\n \treturn 0;\n }\n \n-static int __vsock_bind_dgram(struct vsock_sock *vsk,\n-\t\t\t      struct sockaddr_vm *addr)\n+/**\n+ * vsock_bind_common - bind a socket using the core port allocator\n+ * @vsk: socket to bind\n+ * @addr: local address to bind to\n+ *\n+ * The caller must hold the socket lock.\n+ *\n+ * Return: 0 on success or a negative error code on failure.\n+ */\n+int vsock_bind_common(struct vsock_sock *vsk, struct sockaddr_vm *addr)\n {\n-\treturn vsk-\u003etransport-\u003edgram_bind(vsk, addr);\n+\tint ret;\n+\n+\tspin_lock_bh(\u0026vsock_table_lock);\n+\tret = __vsock_bind_common(vsk, addr);\n+\tspin_unlock_bh(\u0026vsock_table_lock);\n+\n+\treturn ret;\n }\n+EXPORT_SYMBOL_GPL(vsock_bind_common);\n \n static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr)\n {\n@@ -914,13 +1041,11 @@ static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr)\n \tswitch (sk-\u003esk_socket-\u003etype) {\n \tcase SOCK_STREAM:\n \tcase SOCK_SEQPACKET:\n-\t\tspin_lock_bh(\u0026vsock_table_lock);\n-\t\tretval = __vsock_bind_connectible(vsk, addr);\n-\t\tspin_unlock_bh(\u0026vsock_table_lock);\n+\t\tretval = vsock_bind_common(vsk, addr);\n \t\tbreak;\n \n \tcase SOCK_DGRAM:\n-\t\tretval = __vsock_bind_dgram(vsk, addr);\n+\t\tretval = vsk-\u003etransport-\u003edgram_bind(vsk, addr);\n \t\tbreak;\n \n \tdefault:\n@@ -1027,7 +1152,7 @@ static void __vsock_release(struct sock *sk, int level)\n \n \tif (vsk-\u003etransport)\n \t\tvsk-\u003etransport-\u003erelease(vsk);\n-\telse if (sock_type_connectible(sk-\u003esk_type))\n+\telse\n \t\tvsock_remove_sock(vsk);\n \n \tsock_orphan(sk);\n@@ -1331,16 +1456,20 @@ static __poll_t vsock_poll(struct file *file, struct socket *sock,\n \t\tmask |= vsock_poll_shutdown(sk, peer_shutdown);\n \n \t\t/* For datagram sockets we can read if there is something in\n-\t\t * the queue and write as long as the socket isn't shutdown for\n-\t\t * sending.\n+\t\t * the queue and write if the send buffer has space and the\n+\t\t * socket isn't shutdown for sending.\n \t\t */\n \t\tif (!skb_queue_empty_lockless(\u0026sk-\u003esk_receive_queue) ||\n \t\t    (sk-\u003esk_shutdown \u0026 RCV_SHUTDOWN)) {\n \t\t\tmask |= EPOLLIN | EPOLLRDNORM;\n \t\t}\n \n-\t\tif (!(sk-\u003esk_shutdown \u0026 SEND_SHUTDOWN))\n-\t\t\tmask |= EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND;\n+\t\tif (!(sk-\u003esk_shutdown \u0026 SEND_SHUTDOWN)) {\n+\t\t\tif (sock_writeable(sk))\n+\t\t\t\tmask |= EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND;\n+\t\t\telse\n+\t\t\t\tsk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);\n+\t\t}\n \n \t} else if (sock_type_connectible(sk-\u003esk_type)) {\n \t\tconst struct vsock_transport *transport;\n@@ -1466,7 +1595,8 @@ static int vsock_dgram_sendmsg(struct socket *sock, struct msghdr *msg,\n \t\t */\n \n \t\tif (remote_addr-\u003esvm_cid == VMADDR_CID_ANY)\n-\t\t\tremote_addr-\u003esvm_cid = transport-\u003eget_local_cid();\n+\t\t\tremote_addr-\u003esvm_cid = transport-\u003edgram_transport_match ?\n+\t\t\t\tVMADDR_CID_LOCAL : transport-\u003eget_local_cid();\n \n \t\tif (!vsock_addr_bound(remote_addr)) {\n \t\t\terr = -EINVAL;\n@@ -1476,7 +1606,8 @@ static int vsock_dgram_sendmsg(struct socket *sock, struct msghdr *msg,\n \t\tremote_addr = \u0026vsk-\u003eremote_addr;\n \n \t\tif (remote_addr-\u003esvm_cid == VMADDR_CID_ANY)\n-\t\t\tremote_addr-\u003esvm_cid = transport-\u003eget_local_cid();\n+\t\t\tremote_addr-\u003esvm_cid = transport-\u003edgram_transport_match ?\n+\t\t\t\tVMADDR_CID_LOCAL : transport-\u003eget_local_cid();\n \n \t\t/* XXX Should connect() or this function ensure remote_addr is\n \t\t * bound?\n@@ -1490,13 +1621,14 @@ static int vsock_dgram_sendmsg(struct socket *sock, struct msghdr *msg,\n \t\tgoto out;\n \t}\n \n-\tif (!transport-\u003edgram_allow(vsk, remote_addr-\u003esvm_cid,\n-\t\t\t\t    remote_addr-\u003esvm_port)) {\n-\t\terr = -EINVAL;\n+\ttransport = vsock_dgram_get_transport(vsk, remote_addr);\n+\tif (IS_ERR(transport)) {\n+\t\terr = PTR_ERR(transport);\n \t\tgoto out;\n \t}\n \n-\terr = transport-\u003edgram_enqueue(vsk, remote_addr, msg, len);\n+\terr = transport-\u003edgram_enqueue(vsk, transport, remote_addr, msg, len);\n+\tmodule_put(transport-\u003emodule);\n \n out:\n \trelease_sock(sk);\n@@ -1510,6 +1642,7 @@ static int vsock_dgram_connect(struct socket *sock,\n \tstruct sock *sk;\n \tstruct vsock_sock *vsk;\n \tstruct sockaddr_vm *remote_addr;\n+\tconst struct vsock_transport *transport;\n \n \tsk = sock-\u003esk;\n \tvsk = vsock_sk(sk);\n@@ -1531,11 +1664,16 @@ static int vsock_dgram_connect(struct socket *sock,\n \tif (err)\n \t\tgoto out;\n \n-\tif (!vsk-\u003etransport-\u003edgram_allow(vsk, remote_addr-\u003esvm_cid,\n-\t\t\t\t\t remote_addr-\u003esvm_port)) {\n-\t\terr = -EINVAL;\n+\tif (remote_addr-\u003esvm_cid == VMADDR_CID_ANY \u0026\u0026\n+\t    vsk-\u003etransport-\u003edgram_transport_match)\n+\t\tremote_addr-\u003esvm_cid = VMADDR_CID_LOCAL;\n+\n+\ttransport = vsock_dgram_get_transport(vsk, remote_addr);\n+\tif (IS_ERR(transport)) {\n+\t\terr = PTR_ERR(transport);\n \t\tgoto out;\n \t}\n+\tmodule_put(transport-\u003emodule);\n \n \tmemcpy(\u0026vsk-\u003eremote_addr, remote_addr, sizeof(vsk-\u003eremote_addr));\n \tsock-\u003estate = SS_CONNECTED;\n@@ -1604,10 +1742,22 @@ static int vsock_do_ioctl(struct socket *sock, unsigned int cmd,\n \t\t\tbreak;\n \t\t}\n \n-\t\tn_bytes = vsock_stream_has_data(vsk);\n-\t\tif (n_bytes \u003c 0) {\n-\t\t\tret = n_bytes;\n-\t\t\tbreak;\n+\t\tif (sk-\u003esk_type == SOCK_DGRAM) {\n+\t\t\tstruct sk_buff *skb;\n+\n+\t\t\tspin_lock_bh(\u0026sk-\u003esk_receive_queue.lock);\n+\t\t\tskb = skb_peek(\u0026sk-\u003esk_receive_queue);\n+\t\t\tif (skb \u0026\u0026 vsk-\u003etransport-\u003edgram_payload_len)\n+\t\t\t\tn_bytes = vsk-\u003etransport-\u003edgram_payload_len(skb);\n+\t\t\telse\n+\t\t\t\tn_bytes = skb ? skb-\u003elen : 0;\n+\t\t\tspin_unlock_bh(\u0026sk-\u003esk_receive_queue.lock);\n+\t\t} else {\n+\t\t\tn_bytes = vsock_stream_has_data(vsk);\n+\t\t\tif (n_bytes \u003c 0) {\n+\t\t\t\tret = n_bytes;\n+\t\t\t\tbreak;\n+\t\t\t}\n \t\t}\n \t\tret = put_user(n_bytes, arg);\n \t\tbreak;\ndiff --git a/net/vmw_vsock/hyperv_transport.c b/net/vmw_vsock/hyperv_transport.c\nindex 855b15971c359f..6a276ef6964ba1 100644\n--- a/net/vmw_vsock/hyperv_transport.c\n+++ b/net/vmw_vsock/hyperv_transport.c\n@@ -443,6 +443,9 @@ static int hvs_sock_init(struct vsock_sock *vsk, struct vsock_sock *psk)\n \tstruct hvsock *hvs;\n \tstruct sock *sk = sk_vsock(vsk);\n \n+\tif (sk-\u003esk_type == SOCK_DGRAM)\n+\t\treturn -ESOCKTNOSUPPORT;\n+\n \thvs = kzalloc_obj(*hvs);\n \tif (!hvs)\n \t\treturn -ENOMEM;\n@@ -563,6 +566,7 @@ static int hvs_dgram_dequeue(struct vsock_sock *vsk, struct msghdr *msg,\n }\n \n static int hvs_dgram_enqueue(struct vsock_sock *vsk,\n+\t\t\t     const struct vsock_transport *transport,\n \t\t\t     struct sockaddr_vm *remote, struct msghdr *msg,\n \t\t\t     size_t dgram_len)\n {\ndiff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c\nindex 4f9aa9c4c3aa59..d256274088b38c 100644\n--- a/net/vmw_vsock/virtio_transport.c\n+++ b/net/vmw_vsock/virtio_transport.c\n@@ -54,9 +54,11 @@ struct virtio_vsock {\n \tbool rx_run;\n \tint rx_buf_nr;\n \tint rx_buf_max_nr;\n+\tstruct scatterlist in_bufs[MAX_SKB_FRAGS + 1];\n \n \tu32 guest_cid;\n \tbool seqpacket_allow;\n+\tbool dgram_allow;\n \n \t/* These fields are used only in tx path in function\n \t * 'virtio_transport_send_pkt_work()', so to save\n@@ -311,22 +313,31 @@ virtio_transport_cancel_pkt(struct vsock_sock *vsk)\n static void virtio_vsock_rx_fill(struct virtio_vsock *vsock)\n {\n \tint total_len = VIRTIO_VSOCK_DEFAULT_RX_BUF_SIZE;\n-\tstruct scatterlist pkt, *p;\n \tstruct virtqueue *vq;\n \tstruct sk_buff *skb;\n \tint ret;\n \n \tvq = vsock-\u003evqs[VSOCK_VQ_RX];\n+\t/* A datagram must fit in one RX buffer, including its header. */\n+\tif (vsock-\u003edgram_allow)\n+\t\ttotal_len = VIRTIO_VSOCK_SKB_HEADROOM +\n+\t\t\t    VIRTIO_VSOCK_MAX_PKT_BUF_SIZE;\n \n \tdo {\n-\t\tskb = virtio_vsock_alloc_linear_skb(total_len, GFP_KERNEL);\n+\t\tskb = virtio_vsock_alloc_skb(total_len, GFP_KERNEL);\n \t\tif (!skb)\n \t\t\tbreak;\n \n \t\tmemset(skb-\u003ehead, 0, VIRTIO_VSOCK_SKB_HEADROOM);\n-\t\tsg_init_one(\u0026pkt, virtio_vsock_hdr(skb), total_len);\n-\t\tp = \u0026pkt;\n-\t\tret = virtqueue_add_sgs(vq, \u0026p, 0, 1, skb, GFP_KERNEL);\n+\t\t/* Post the header and the entire payload as one RX buffer. */\n+\t\tvirtio_vsock_skb_put(skb, total_len - VIRTIO_VSOCK_SKB_HEADROOM);\n+\t\tskb_push(skb, VIRTIO_VSOCK_SKB_HEADROOM);\n+\t\tsg_init_table(vsock-\u003ein_bufs, ARRAY_SIZE(vsock-\u003ein_bufs));\n+\t\tret = skb_to_sgvec(skb, vsock-\u003ein_bufs, 0, total_len);\n+\t\tskb_pull(skb, VIRTIO_VSOCK_SKB_HEADROOM);\n+\t\tif (ret \u003e= 0)\n+\t\t\tret = virtqueue_add_inbuf(vq, vsock-\u003ein_bufs, ret, skb,\n+\t\t\t\t\t\t  GFP_KERNEL);\n \t\tif (ret \u003c 0) {\n \t\t\tkfree_skb(skb);\n \t\t\tbreak;\n@@ -547,6 +558,18 @@ bool virtio_transport_stream_allow(struct vsock_sock *vsk, u32 cid, u32 port)\n \n static bool virtio_transport_seqpacket_allow(struct vsock_sock *vsk,\n \t\t\t\t\t     u32 remote_cid);\n+static bool virtio_transport_dgram_allow_check(struct vsock_sock *vsk,\n+\t\t\t\t\t       u32 cid, u32 port);\n+\n+static int virtio_transport_socket_init(struct vsock_sock *vsk,\n+\t\t\t\t\tstruct vsock_sock *psk)\n+{\n+\tif (sk_vsock(vsk)-\u003esk_type == SOCK_DGRAM \u0026\u0026\n+\t    !virtio_transport_dgram_allow_check(vsk, 0, 0))\n+\t\treturn -ESOCKTNOSUPPORT;\n+\n+\treturn virtio_transport_do_socket_init(vsk, psk);\n+}\n \n static bool virtio_transport_has_remote_cid(struct vsock_sock *vsk, u32 cid)\n {\n@@ -561,7 +584,7 @@ static struct virtio_transport virtio_transport = {\n \t\t.get_local_cid            = virtio_transport_get_local_cid,\n \t\t.has_remote_cid           = virtio_transport_has_remote_cid,\n \n-\t\t.init                     = virtio_transport_do_socket_init,\n+\t\t.init                     = virtio_transport_socket_init,\n \t\t.destruct                 = virtio_transport_destruct,\n \t\t.release                  = virtio_transport_release,\n \t\t.connect                  = virtio_transport_connect,\n@@ -571,7 +594,8 @@ static struct virtio_transport virtio_transport = {\n \t\t.dgram_bind               = virtio_transport_dgram_bind,\n \t\t.dgram_dequeue            = virtio_transport_dgram_dequeue,\n \t\t.dgram_enqueue            = virtio_transport_dgram_enqueue,\n-\t\t.dgram_allow              = virtio_transport_dgram_allow,\n+\t\t.dgram_allow              = virtio_transport_dgram_allow_check,\n+\t\t.dgram_transport_match    = virtio_transport_dgram_transport_match,\n \n \t\t.stream_dequeue           = virtio_transport_stream_dequeue,\n \t\t.stream_enqueue           = virtio_transport_stream_enqueue,\n@@ -629,6 +653,25 @@ virtio_transport_seqpacket_allow(struct vsock_sock *vsk, u32 remote_cid)\n \treturn seqpacket_allow;\n }\n \n+static bool\n+virtio_transport_dgram_allow_check(struct vsock_sock *vsk, u32 cid, u32 port)\n+{\n+\tstruct virtio_vsock *vsock;\n+\tbool dgram_allow;\n+\n+\tif (!vsock_net_mode_global(vsk))\n+\t\treturn false;\n+\n+\tdgram_allow = false;\n+\trcu_read_lock();\n+\tvsock = rcu_dereference(the_virtio_vsock);\n+\tif (vsock)\n+\t\tdgram_allow = vsock-\u003edgram_allow;\n+\trcu_read_unlock();\n+\n+\treturn dgram_allow;\n+}\n+\n static void virtio_transport_rx_work(struct work_struct *work)\n {\n \tstruct virtio_vsock *vsock =\n@@ -665,7 +708,7 @@ static void virtio_transport_rx_work(struct work_struct *work)\n \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\t     len \u003e sizeof(*hdr) + skb-\u003elen)) {\n \t\t\t\tkfree_skb(skb);\n \t\t\t\tcontinue;\n \t\t\t}\n@@ -677,8 +720,7 @@ static void virtio_transport_rx_work(struct work_struct *work)\n \t\t\t\tcontinue;\n \t\t\t}\n \n-\t\t\tif (payload_len)\n-\t\t\t\tvirtio_vsock_skb_put(skb, payload_len);\n+\t\t\tpskb_trim_unique(skb, payload_len);\n \n \t\t\tvirtio_transport_deliver_tap_pkt(skb);\n \n@@ -690,7 +732,8 @@ static void virtio_transport_rx_work(struct work_struct *work)\n \t} while (!virtqueue_enable_cb(vq));\n \n out:\n-\tif (vsock-\u003erx_buf_nr \u003c vsock-\u003erx_buf_max_nr / 2)\n+\tif (!vsock-\u003erx_buf_nr ||\n+\t    vsock-\u003erx_buf_nr \u003c vsock-\u003erx_buf_max_nr / 2)\n \t\tvirtio_vsock_rx_fill(vsock);\n out_nofill:\n \tmutex_unlock(\u0026vsock-\u003erx_lock);\n@@ -835,6 +878,9 @@ static int virtio_vsock_probe(struct virtio_device *vdev)\n \tif (virtio_has_feature(vdev, VIRTIO_VSOCK_F_SEQPACKET))\n \t\tvsock-\u003eseqpacket_allow = true;\n \n+\tif (virtio_has_feature(vdev, VIRTIO_VSOCK_F_DGRAM))\n+\t\tvsock-\u003edgram_allow = true;\n+\n \tvdev-\u003epriv = vsock;\n \n \tret = virtio_vsock_vqs_init(vsock);\n@@ -932,7 +978,8 @@ static struct virtio_device_id id_table[] = {\n };\n \n static unsigned int features[] = {\n-\tVIRTIO_VSOCK_F_SEQPACKET\n+\tVIRTIO_VSOCK_F_SEQPACKET,\n+\tVIRTIO_VSOCK_F_DGRAM,\n };\n \n static struct virtio_driver virtio_vsock_driver = {\ndiff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c\nindex 1e762a480df490..7c1ec8feb853c9 100644\n--- a/net/vmw_vsock/virtio_transport_common.c\n+++ b/net/vmw_vsock/virtio_transport_common.c\n@@ -204,10 +204,18 @@ static u16 virtio_transport_get_type(struct sock *sk)\n {\n \tif (sk-\u003esk_type == SOCK_STREAM)\n \t\treturn VIRTIO_VSOCK_TYPE_STREAM;\n+\telse if (sk-\u003esk_type == SOCK_DGRAM)\n+\t\treturn VIRTIO_VSOCK_TYPE_DGRAM;\n \telse\n \t\treturn VIRTIO_VSOCK_TYPE_SEQPACKET;\n }\n \n+static void virtio_transport_dgram_skb_destructor(struct sk_buff *skb)\n+{\n+\tvirtio_transport_consume_skb_sent(skb, false);\n+\tsock_wfree(skb);\n+}\n+\n /* Returns new sk_buff on success, otherwise returns NULL. */\n static struct sk_buff *virtio_transport_alloc_skb(struct virtio_vsock_pkt_info *info,\n \t\t\t\t\t\t  size_t payload_len,\n@@ -303,6 +311,7 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,\n \tu32 src_cid, src_port, dst_cid, dst_port;\n \tconst struct virtio_transport *t_ops;\n \tstruct iov_iter_state msg_iter_state;\n+\tstruct sock *sk = sk_vsock(vsk);\n \tstruct virtio_vsock_sock *vvs;\n \tstruct ubuf_info *uarg = NULL;\n \tu32 pkt_len = info-\u003epkt_len;\n@@ -313,13 +322,18 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,\n \n \tinfo-\u003etype = virtio_transport_get_type(sk_vsock(vsk));\n \n-\tt_ops = virtio_transport_get_ops(vsk);\n+\tt_ops = info-\u003et_ops ?: virtio_transport_get_ops(vsk);\n \tif (unlikely(!t_ops))\n \t\treturn -EFAULT;\n \n \tsrc_cid = t_ops-\u003etransport.get_local_cid();\n+\t/* Preserve explicit datagram bindings so loopback replies can find them. */\n+\tif (info-\u003etype == VIRTIO_VSOCK_TYPE_DGRAM \u0026\u0026\n+\t    src_cid == VMADDR_CID_LOCAL \u0026\u0026\n+\t    vsk-\u003elocal_addr.svm_cid != VMADDR_CID_ANY)\n+\t\tsrc_cid = vsk-\u003elocal_addr.svm_cid;\n \tsrc_port = vsk-\u003elocal_addr.svm_port;\n-\tif (!info-\u003eremote_cid) {\n+\tif (info-\u003etype != VIRTIO_VSOCK_TYPE_DGRAM \u0026\u0026 !info-\u003eremote_cid) {\n \t\tdst_cid\t= vsk-\u003eremote_addr.svm_cid;\n \t\tdst_port = vsk-\u003eremote_addr.svm_port;\n \t} else {\n@@ -329,12 +343,24 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,\n \n \tvvs = vsk-\u003etrans;\n \n-\t/* virtio_transport_get_credit might return less than pkt_len credit */\n-\tpkt_len = virtio_transport_get_credit(vvs, pkt_len);\n+\tif (info-\u003etype == VIRTIO_VSOCK_TYPE_DGRAM) {\n+\t\tif (pkt_len \u003e VIRTIO_VSOCK_MAX_PKT_BUF_SIZE)\n+\t\t\treturn -EMSGSIZE;\n+\n+\t\t/* Datagram sends are serialized by the socket lock. */\n+\t\tif (sk_wmem_alloc_get(sk) \u003e= READ_ONCE(sk-\u003esk_sndbuf)) {\n+\t\t\tsk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);\n+\t\t\tset_bit(SOCK_NOSPACE, \u0026sk-\u003esk_socket-\u003eflags);\n+\t\t\treturn -EAGAIN;\n+\t\t}\n+\t} else {\n+\t\t/* virtio_transport_get_credit might return less than pkt_len credit */\n+\t\tpkt_len = virtio_transport_get_credit(vvs, pkt_len);\n \n-\t/* Do not send zero length OP_RW pkt */\n-\tif (pkt_len == 0 \u0026\u0026 info-\u003eop == VIRTIO_VSOCK_OP_RW)\n-\t\treturn pkt_len;\n+\t\t/* Do not send zero length OP_RW pkt */\n+\t\tif (pkt_len == 0 \u0026\u0026 info-\u003eop == VIRTIO_VSOCK_OP_RW)\n+\t\t\treturn pkt_len;\n+\t}\n \n \tif (info-\u003emsg \u0026\u0026 (info-\u003emsg-\u003emsg_flags \u0026 MSG_ZEROCOPY)) {\n \t\t/* If 'info-\u003emsg' is not NULL, this is only VIRTIO_VSOCK_OP_RW.\n@@ -348,7 +374,8 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,\n \t\t\tuarg = msg_zerocopy_realloc(sk_vsock(vsk), pkt_len,\n \t\t\t\t\t\t    NULL, false);\n \t\t\tif (!uarg) {\n-\t\t\t\tvirtio_transport_put_credit(vvs, pkt_len);\n+\t\t\t\tif (info-\u003etype != VIRTIO_VSOCK_TYPE_DGRAM)\n+\t\t\t\t\tvirtio_transport_put_credit(vvs, pkt_len);\n \t\t\t\treturn -ENOMEM;\n \t\t\t}\n \n@@ -393,7 +420,15 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,\n \t\t\tbreak;\n \t\t}\n \n-\t\tvirtio_transport_inc_tx_pkt(vvs, skb);\n+\t\tif (info-\u003etype == VIRTIO_VSOCK_TYPE_DGRAM) {\n+\t\t\tspin_lock_bh(\u0026vvs-\u003etx_lock);\n+\t\t\tvvs-\u003ebytes_unsent += skb_len;\n+\t\t\tspin_unlock_bh(\u0026vvs-\u003etx_lock);\n+\t\t\tVIRTIO_VSOCK_SKB_CB(skb)-\u003etx_accounted = true;\n+\t\t\tskb-\u003edestructor = virtio_transport_dgram_skb_destructor;\n+\t\t} else {\n+\t\t\tvirtio_transport_inc_tx_pkt(vvs, skb);\n+\t\t}\n \n \t\tret = t_ops-\u003esend_pkt(skb, info-\u003enet);\n \t\tif (ret \u003c 0)\n@@ -416,7 +451,8 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,\n \tif (info-\u003emsg \u0026\u0026 ret \u003c 0)\n \t\tiov_iter_restore(\u0026info-\u003emsg-\u003emsg_iter, \u0026msg_iter_state);\n \n-\tvirtio_transport_put_credit(vvs, rest_len);\n+\tif (info-\u003etype != VIRTIO_VSOCK_TYPE_DGRAM)\n+\t\tvirtio_transport_put_credit(vvs, rest_len);\n \n \t/* msg_zerocopy_realloc() initializes the ubuf_info refcnt to 1.\n \t * skb_zcopy_set() increases it for each skb, so we can drop that\n@@ -620,7 +656,9 @@ void virtio_transport_consume_skb_sent(struct sk_buff *skb, bool consume)\n {\n \tstruct sock *s = skb-\u003esk;\n \n-\tif (s \u0026\u0026 skb-\u003elen) {\n+\tif (s \u0026\u0026 skb-\u003elen \u0026\u0026\n+\t    (s-\u003esk_type != SOCK_DGRAM ||\n+\t     VIRTIO_VSOCK_SKB_CB(skb)-\u003etx_accounted)) {\n \t\tstruct vsock_sock *vs = vsock_sk(s);\n \t\tstruct virtio_vsock_sock *vvs;\n \n@@ -631,6 +669,9 @@ void virtio_transport_consume_skb_sent(struct sk_buff *skb, bool consume)\n \t\tspin_unlock_bh(\u0026vvs-\u003etx_lock);\n \t}\n \n+\tif (s \u0026\u0026 s-\u003esk_type == SOCK_DGRAM)\n+\t\tVIRTIO_VSOCK_SKB_CB(skb)-\u003etx_accounted = false;\n+\n \tif (consume)\n \t\tconsume_skb(skb);\n }\n@@ -999,7 +1040,49 @@ virtio_transport_dgram_dequeue(struct vsock_sock *vsk,\n \t\t\t       struct msghdr *msg,\n \t\t\t       size_t len, int flags)\n {\n-\treturn -EOPNOTSUPP;\n+\tstruct sock *sk = sk_vsock(vsk);\n+\tstruct virtio_vsock_hdr *hdr;\n+\tstruct sk_buff *skb;\n+\tsize_t payload_len;\n+\tint err;\n+\n+\tif (flags \u0026 (MSG_OOB | MSG_ERRQUEUE))\n+\t\treturn -EOPNOTSUPP;\n+\n+\tskb = skb_recv_datagram(sk, flags, \u0026err);\n+\tif (!skb)\n+\t\treturn err;\n+\n+\thdr = virtio_vsock_hdr(skb);\n+\tpayload_len = skb-\u003elen;\n+\n+\tif (payload_len \u003e len) {\n+\t\tpayload_len = len;\n+\t\tmsg-\u003emsg_flags |= MSG_TRUNC;\n+\t}\n+\n+\terr = skb_copy_datagram_iter(skb, 0, \u0026msg-\u003emsg_iter, payload_len);\n+\tif (err)\n+\t\tgoto out;\n+\n+\tif (msg-\u003emsg_name) {\n+\t\tDECLARE_SOCKADDR(struct sockaddr_vm *, vm_addr, msg-\u003emsg_name);\n+\n+\t\tvsock_addr_init(vm_addr, le64_to_cpu(hdr-\u003esrc_cid),\n+\t\t\t\tle32_to_cpu(hdr-\u003esrc_port));\n+\t\tmsg-\u003emsg_namelen = sizeof(*vm_addr);\n+\t\tif (VIRTIO_VSOCK_SKB_CB(skb)-\u003eto_host)\n+\t\t\tvm_addr-\u003esvm_flags = VMADDR_FLAG_TO_HOST;\n+\t}\n+\n+\tif (flags \u0026 MSG_TRUNC)\n+\t\terr = skb-\u003elen;\n+\telse\n+\t\terr = payload_len;\n+\n+out:\n+\tskb_free_datagram(sk, skb);\n+\treturn err;\n }\n EXPORT_SYMBOL_GPL(virtio_transport_dgram_dequeue);\n \n@@ -1202,16 +1285,23 @@ EXPORT_SYMBOL_GPL(virtio_transport_stream_is_active);\n int virtio_transport_dgram_bind(struct vsock_sock *vsk,\n \t\t\t\tstruct sockaddr_vm *addr)\n {\n-\treturn -EOPNOTSUPP;\n+\treturn vsock_bind_common(vsk, addr);\n }\n EXPORT_SYMBOL_GPL(virtio_transport_dgram_bind);\n \n bool virtio_transport_dgram_allow(struct vsock_sock *vsk, u32 cid, u32 port)\n {\n-\treturn false;\n+\treturn true;\n }\n EXPORT_SYMBOL_GPL(virtio_transport_dgram_allow);\n \n+bool virtio_transport_dgram_transport_match(const struct vsock_transport *t)\n+{\n+\treturn t \u0026\u0026 t-\u003edgram_transport_match ==\n+\t\tvirtio_transport_dgram_transport_match;\n+}\n+EXPORT_SYMBOL_GPL(virtio_transport_dgram_transport_match);\n+\n int virtio_transport_connect(struct vsock_sock *vsk)\n {\n \tstruct virtio_vsock_pkt_info info = {\n@@ -1242,11 +1332,23 @@ EXPORT_SYMBOL_GPL(virtio_transport_shutdown);\n \n int\n virtio_transport_dgram_enqueue(struct vsock_sock *vsk,\n+\t\t\t       const struct vsock_transport *transport,\n \t\t\t       struct sockaddr_vm *remote_addr,\n \t\t\t       struct msghdr *msg,\n \t\t\t       size_t dgram_len)\n {\n-\treturn -EOPNOTSUPP;\n+\tstruct virtio_vsock_pkt_info info = {\n+\t\t.op = VIRTIO_VSOCK_OP_RW,\n+\t\t.t_ops = container_of(transport, struct virtio_transport, transport),\n+\t\t.msg = msg,\n+\t\t.pkt_len = dgram_len,\n+\t\t.vsk = vsk,\n+\t\t.remote_cid = remote_addr-\u003esvm_cid,\n+\t\t.remote_port = remote_addr-\u003esvm_port,\n+\t\t.net = sock_net(sk_vsock(vsk)),\n+\t};\n+\n+\treturn virtio_transport_send_pkt_info(vsk, \u0026info);\n }\n EXPORT_SYMBOL_GPL(virtio_transport_dgram_enqueue);\n \n@@ -1771,7 +1873,35 @@ virtio_transport_recv_listen(struct sock *sk, struct sk_buff *skb,\n static bool virtio_transport_valid_type(u16 type)\n {\n \treturn (type == VIRTIO_VSOCK_TYPE_STREAM) ||\n-\t       (type == VIRTIO_VSOCK_TYPE_SEQPACKET);\n+\t       (type == VIRTIO_VSOCK_TYPE_SEQPACKET) ||\n+\t       (type == VIRTIO_VSOCK_TYPE_DGRAM);\n+}\n+\n+static bool\n+virtio_transport_dgram_source_allow(struct vsock_sock *vsk,\n+\t\t\t\t    const struct vsock_transport *transport,\n+\t\t\t\t    const struct sockaddr_vm *src)\n+{\n+\tconst struct vsock_transport *peer_transport;\n+\tstruct sockaddr_vm peer = vsk-\u003eremote_addr;\n+\tbool allow;\n+\n+\tif (!virtio_transport_dgram_transport_match(vsk-\u003etransport) ||\n+\t    !virtio_transport_dgram_transport_match(transport))\n+\t\treturn false;\n+\t/* Shutdown preserves the peer; only disconnect removes it. */\n+\tif (sk_vsock(vsk)-\u003esk_socket-\u003estate == SS_UNCONNECTED)\n+\t\treturn true;\n+\n+\tpeer_transport = vsock_dgram_get_transport(vsk, \u0026peer);\n+\tif (IS_ERR(peer_transport))\n+\t\treturn false;\n+\tallow = peer_transport == transport \u0026\u0026\n+\t\tsrc-\u003esvm_port == vsk-\u003eremote_addr.svm_port \u0026\u0026\n+\t\t(src-\u003esvm_cid == vsk-\u003eremote_addr.svm_cid ||\n+\t\t transport-\u003eget_local_cid() == VMADDR_CID_LOCAL);\n+\tmodule_put(peer_transport-\u003emodule);\n+\treturn allow;\n }\n \n /* We are under the virtio-vsock's vsock-\u003erx_lock or vhost-vsock's vq-\u003emutex\n@@ -1805,6 +1935,41 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,\n \t\tgoto free_pkt;\n \t}\n \n+\t/* Dgram packets are delivered directly to bound sockets without\n+\t * going through the connection state machine. Undeliverable\n+\t * datagrams are silently dropped (no RST).\n+\t */\n+\tif (le16_to_cpu(hdr-\u003etype) == VIRTIO_VSOCK_TYPE_DGRAM) {\n+\t\tif (le16_to_cpu(hdr-\u003eop) != VIRTIO_VSOCK_OP_RW)\n+\t\t\tgoto free_pkt;\n+\n+\t\tsk = vsock_find_bound_socket_net(\u0026dst, net);\n+\n+\t\tif (!sk || sk-\u003esk_type != SOCK_DGRAM) {\n+\t\t\tif (sk)\n+\t\t\t\tsock_put(sk);\n+\t\t\tgoto free_pkt;\n+\t\t}\n+\n+\t\tvsk = vsock_sk(sk);\n+\t\tlock_sock(sk);\n+\t\tif (sock_flag(sk, SOCK_DEAD) ||\n+\t\t    !virtio_transport_dgram_source_allow(vsk, \u0026t-\u003etransport, \u0026src)) {\n+\t\t\trelease_sock(sk);\n+\t\t\tsock_put(sk);\n+\t\t\tgoto free_pkt;\n+\t\t}\n+\n+\t\tVIRTIO_VSOCK_SKB_CB(skb)-\u003eto_host =\n+\t\t\tt-\u003etransport.get_local_cid() \u003e VMADDR_CID_HOST;\n+\t\tif (sock_queue_rcv_skb(sk, skb))\n+\t\t\tkfree_skb(skb);\n+\n+\t\trelease_sock(sk);\n+\t\tsock_put(sk);\n+\t\treturn;\n+\t}\n+\n \t/* The socket must be in connected or bound table\n \t * otherwise send reset back\n \t */\ndiff --git a/net/vmw_vsock/vmci_transport.c b/net/vmw_vsock/vmci_transport.c\nindex 1f186e8f83640a..410deb2493c633 100644\n--- a/net/vmw_vsock/vmci_transport.c\n+++ b/net/vmw_vsock/vmci_transport.c\n@@ -1723,6 +1723,7 @@ static int vmci_transport_dgram_bind(struct vsock_sock *vsk,\n \n static int vmci_transport_dgram_enqueue(\n \tstruct vsock_sock *vsk,\n+\tconst struct vsock_transport *transport,\n \tstruct sockaddr_vm *remote_addr,\n \tstruct msghdr *msg,\n \tsize_t len)\n@@ -1761,6 +1762,14 @@ static int vmci_transport_dgram_enqueue(\n \treturn err - sizeof(*dg);\n }\n \n+static size_t vmci_transport_dgram_payload_len(const struct sk_buff *skb)\n+{\n+\tif (skb-\u003elen \u003c sizeof(struct vmci_datagram))\n+\t\treturn 0;\n+\n+\treturn skb-\u003elen - sizeof(struct vmci_datagram);\n+}\n+\n static int vmci_transport_dgram_dequeue(struct vsock_sock *vsk,\n \t\t\t\t\tstruct msghdr *msg, size_t len,\n \t\t\t\t\tint flags)\n@@ -2075,6 +2084,7 @@ static struct vsock_transport vmci_transport = {\n \t.connect = vmci_transport_connect,\n \t.dgram_bind = vmci_transport_dgram_bind,\n \t.dgram_dequeue = vmci_transport_dgram_dequeue,\n+\t.dgram_payload_len = vmci_transport_dgram_payload_len,\n \t.dgram_enqueue = vmci_transport_dgram_enqueue,\n \t.dgram_allow = vmci_transport_dgram_allow,\n \t.stream_dequeue = vmci_transport_stream_dequeue,\ndiff --git a/net/vmw_vsock/vsock_bpf.c b/net/vmw_vsock/vsock_bpf.c\nindex 9049d26486460c..296e1fa7970b77 100644\n--- a/net/vmw_vsock/vsock_bpf.c\n+++ b/net/vmw_vsock/vsock_bpf.c\n@@ -161,7 +161,8 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore\n \tif (!vsk-\u003etransport)\n \t\treturn -ENODEV;\n \n-\tif (!vsk-\u003etransport-\u003eread_skb)\n+\t/* Datagram receive is not implemented by the sockmap callbacks. */\n+\tif (sk-\u003esk_type == SOCK_DGRAM || !vsk-\u003etransport-\u003eread_skb)\n \t\treturn -EOPNOTSUPP;\n \n \tvsock_bpf_check_needs_rebuild(psock-\u003esk_proto);\ndiff --git a/net/vmw_vsock/vsock_loopback.c b/net/vmw_vsock/vsock_loopback.c\nindex 8068d1b6e85181..d7bff1da2b7486 100644\n--- a/net/vmw_vsock/vsock_loopback.c\n+++ b/net/vmw_vsock/vsock_loopback.c\n@@ -77,6 +77,7 @@ static struct virtio_transport loopback_transport = {\n \t\t.dgram_dequeue            = virtio_transport_dgram_dequeue,\n \t\t.dgram_enqueue            = virtio_transport_dgram_enqueue,\n \t\t.dgram_allow              = virtio_transport_dgram_allow,\n+\t\t.dgram_transport_match    = virtio_transport_dgram_transport_match,\n \n \t\t.stream_dequeue           = virtio_transport_stream_dequeue,\n \t\t.stream_enqueue           = virtio_transport_stream_enqueue,\ndiff --git a/tools/testing/vsock/Makefile b/tools/testing/vsock/Makefile\nindex 88211fd132d23e..78e2cb7b1193dd 100644\n--- a/tools/testing/vsock/Makefile\n+++ b/tools/testing/vsock/Makefile\n@@ -1,7 +1,8 @@\n # SPDX-License-Identifier: GPL-2.0-only\n all: test vsock_perf\n test: vsock_test vsock_diag_test vsock_uring_test\n-vsock_test: vsock_test.o vsock_test_zerocopy.o timeout.o control.o util.o msg_zerocopy_common.o\n+vsock_test: vsock_test.o vsock_test_zerocopy.o vsock_test_dgram.o \\\n+\t    timeout.o control.o util.o msg_zerocopy_common.o\n vsock_diag_test: vsock_diag_test.o timeout.o control.o util.o\n vsock_perf: vsock_perf.o msg_zerocopy_common.o\n \ndiff --git a/tools/testing/vsock/README b/tools/testing/vsock/README\nindex 680ce666ceb56d..be34b032ef1351 100644\n--- a/tools/testing/vsock/README\n+++ b/tools/testing/vsock/README\n@@ -5,7 +5,7 @@ Hyper-V.\n \n The following tests are available:\n \n-  * vsock_test - core AF_VSOCK socket functionality\n+  * vsock_test - stream, seqpacket and datagram functionality\n   * vsock_diag_test - vsock_diag.ko module for listing open sockets\n \n The following prerequisite steps are not automated and must be performed prior\n@@ -32,7 +32,7 @@ Invoke test binaries in both directions as follows:\n                         --control-port=1234 \\\n                         --peer-cid=2\n   (host)# $TEST_BINARY --mode=client \\\n-                       --control-port=$GUEST_IP \\\n+                       --control-host=$GUEST_IP \\\n                        --control-port=1234 \\\n                        --peer-cid=3\n \n@@ -51,6 +51,25 @@ suite run, e.g.\n \n For more information see Documentation/dev-tools/kmemleak.rst.\n \n+Datagram tests\n+--------------\n+vsock_test includes datagram cases in the client/server runs above. Unsupported\n+cases print a skip reason.\n+\n+Virtio/vhost datagrams require kernel support on both sides and a VMM that\n+negotiates VIRTIO_VSOCK_F_DGRAM. Load vsock_loopback on both host and guest for\n+the local cases, and make /dev/vsock available. VMCI's dedicated datagram\n+transport takes priority when loaded, causing virtio/vhost cases to skip.\n+\n+Run only the local datagram cases without a peer or TCP control connection:\n+\n+  ./vsock_test --local\n+\n+Use --list and --pick \u003ctest_id\u003e to select individual cases. The BPF admission\n+test (48, requiring BPF privileges) and nested routing test (49, requiring both\n+virtio and vhost transports) need explicit selection. The VMCI queue-length\n+test (53) runs only with VMCI.\n+\n vsock_perf utility\n -------------------\n 'vsock_perf' is a simple tool to measure vsock performance. It works in\ndiff --git a/tools/testing/vsock/util.c b/tools/testing/vsock/util.c\nindex 299a4e8ecb6345..6ad97403697f18 100644\n--- a/tools/testing/vsock/util.c\n+++ b/tools/testing/vsock/util.c\n@@ -499,47 +499,55 @@ void run_tests(const struct test_case *test_cases,\n \tint i;\n \n \tfor (i = 0; test_cases[i].name; i++) {\n+\t\tconst struct test_case *test = \u0026test_cases[i];\n \t\tvoid (*run)(const struct test_opts *opts);\n+\t\tconst char *reason;\n \t\tchar *line;\n \n-\t\tprintf(\"%d - %s...\", i, test_cases[i].name);\n+\t\tif (opts-\u003elocal \u0026\u0026 !test-\u003elocal)\n+\t\t\tcontinue;\n+\t\tprintf(\"%d - %s...\", i, test-\u003ename);\n \t\tfflush(stdout);\n \n-\t\t/* Full barrier before executing the next test.  This\n-\t\t * ensures that client and server are executing the\n-\t\t * same test case.  In particular, it means whoever is\n-\t\t * faster will not see the peer still executing the\n-\t\t * last test.  This is important because port numbers\n-\t\t * can be used by multiple test cases.\n-\t\t */\n-\t\tif (test_cases[i].skip)\n-\t\t\tcontrol_writeln(\"SKIP\");\n-\t\telse\n-\t\t\tcontrol_writeln(\"NEXT\");\n-\n-\t\tline = control_readln();\n-\t\tif (control_cmpln(line, \"SKIP\", false) || test_cases[i].skip) {\n-\n-\t\t\tprintf(\"skipped\\n\");\n-\n+\t\tif (opts-\u003elocal) {\n+\t\t\tif (test-\u003eskip) {\n+\t\t\t\tprintf(\"skipped\\n\");\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t} else {\n+\t\t\t/* Agree on explicit selection before any capability check\n+\t\t\t * or test can exchange messages on the control connection.\n+\t\t\t */\n+\t\t\tcontrol_writeln(test-\u003eskip ? \"SKIP\" : \"NEXT\");\n+\t\t\tline = control_readln();\n+\t\t\tif (control_cmpln(line, \"SKIP\", false) || test-\u003eskip) {\n+\t\t\t\tprintf(\"skipped\\n\");\n+\t\t\t\tfree(line);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tcontrol_cmpln(line, \"NEXT\", true);\n \t\t\tfree(line);\n-\t\t\tcontinue;\n \t\t}\n \n-\t\tcontrol_cmpln(line, \"NEXT\", true);\n-\t\tfree(line);\n-\n-\t\tif (opts-\u003emode == TEST_MODE_CLIENT)\n-\t\t\trun = test_cases[i].run_client;\n+\t\tif (test-\u003echeck) {\n+\t\t\treason = test-\u003echeck(opts);\n+\t\t\tif (reason) {\n+\t\t\t\tprintf(\"skipped: %s\\n\", reason);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\t\tif (opts-\u003elocal || opts-\u003emode == TEST_MODE_CLIENT)\n+\t\t\trun = test-\u003erun_client;\n \t\telse\n-\t\t\trun = test_cases[i].run_server;\n-\n+\t\t\trun = test-\u003erun_server;\n \t\tif (run)\n \t\t\trun(opts);\n-\n \t\tprintf(\"ok\\n\");\n \t}\n \n+\tif (opts-\u003elocal)\n+\t\treturn;\n+\n \tprintf(\"All tests have been executed. Waiting other peer...\");\n \tfflush(stdout);\n \ndiff --git a/tools/testing/vsock/util.h b/tools/testing/vsock/util.h\nindex 9dabb547021edc..e23290e7dcc64f 100644\n--- a/tools/testing/vsock/util.h\n+++ b/tools/testing/vsock/util.h\n@@ -51,6 +51,10 @@ struct test_opts {\n \tenum test_mode mode;\n \tunsigned int peer_cid;\n \tunsigned int peer_port;\n+\tunsigned int bind_cid;\n+\tbool local;\n+\tbool to_host;\n+\tbool expect_to_host;\n };\n \n /* A test case definition.  Test functions must print failures to stderr and\n@@ -66,6 +70,12 @@ struct test_case {\n \tvoid (*run_server)(const struct test_opts *opts);\n \n \tbool skip;\n+\tbool local; /* Can run without a control connection */\n+\n+\t/* Return a skip reason or NULL after the selection barrier. Checks\n+\t * that coordinate over the control channel must agree on both peers.\n+\t */\n+\tconst char *(*check)(const struct test_opts *opts);\n };\n \n void init_signals(void);\ndiff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c\nindex 10b9074fb87f7f..f4ff6a42a9cb17 100644\n--- a/tools/testing/vsock/vsock_test.c\n+++ b/tools/testing/vsock/vsock_test.c\n@@ -27,6 +27,7 @@\n #include \u003clinux/sockios.h\u003e\n \n #include \"vsock_test_zerocopy.h\"\n+#include \"vsock_test_dgram.h\"\n #include \"timeout.h\"\n #include \"control.h\"\n #include \"util.h\"\n@@ -2911,6 +2912,95 @@ static struct test_case test_cases[] = {\n \t{\n \t\t.name = \"SO_INQ option validation\",\n \t\t.run_client = test_so_inq_validation_client,\n+\t},\n+\t\t.name = \"SOCK_DGRAM autobind, duplicate bind and wildcard destination\",\n+\t\t.run_client = test_dgram_bind,\n+\t\t.run_server = test_dgram_bind,\n+\t\t.check = vsock_dgram_check_local,\n+\t\t.local = true,\n+\t},\n+\t{\n+\t\t.name = \"SOCK_DGRAM queue ioctls, poll, peek, truncation and empty messages\",\n+\t\t.run_client = test_dgram_receive,\n+\t\t.run_server = test_dgram_receive,\n+\t\t.check = vsock_dgram_check_local,\n+\t\t.local = true,\n+\t},\n+\t{\n+\t\t.name = \"SOCK_DGRAM unsupported receive flags preserve normal data\",\n+\t\t.run_client = test_dgram_receive_flags,\n+\t\t.run_server = test_dgram_receive_flags,\n+\t\t.check = vsock_dgram_check_local,\n+\t\t.local = true,\n+\t},\n+\t{\n+\t\t.name = \"SOCK_DGRAM socket filter trims receive length\",\n+\t\t.run_client = test_dgram_filter,\n+\t\t.run_server = test_dgram_filter,\n+\t\t.check = vsock_dgram_check_local,\n+\t\t.local = true,\n+\t},\n+\t{\n+\t\t.name = \"SOCK_DGRAM connect, disconnect and shutdown peer filtering\",\n+\t\t.run_client = test_dgram_connect,\n+\t\t.run_server = test_dgram_connect,\n+\t\t.check = vsock_dgram_check_local,\n+\t\t.local = true,\n+\t},\n+\t{\n+\t\t.name = \"SOCK_DGRAM explicit loopback source CIDs and reply addresses\",\n+\t\t.run_client = test_dgram_source_cid,\n+\t\t.run_server = test_dgram_source_cid,\n+\t\t.check = vsock_dgram_check_local,\n+\t\t.local = true,\n+\t},\n+\t{\n+\t\t.name = \"SOCK_DGRAM connected stream/seqpacket cannot shadow datagrams\",\n+\t\t.run_client = test_dgram_bound_lookup,\n+\t\t.run_server = test_dgram_bound_lookup,\n+\t\t.check = vsock_dgram_check_local,\n+\t\t.local = true,\n+\t},\n+\t{\n+\t\t.name = \"SOCK_DGRAM sockmap and sockhash reject datagrams (BPF privileges)\",\n+\t\t.run_client = test_dgram_sockmap,\n+\t\t.run_server = test_dgram_sockmap,\n+\t\t.skip = true,\n+\t\t.check = vsock_dgram_check_local,\n+\t\t.local = true,\n+\t},\n+\t{\n+\t\t.name = \"SOCK_DGRAM nested CID_HOST loopback rejection (nested VM only)\",\n+\t\t.run_client = test_dgram_nested_source,\n+\t\t.run_server = test_dgram_nested_source,\n+\t\t.skip = true,\n+\t\t.check = vsock_dgram_check_local,\n+\t\t.local = true,\n+\t},\n+\t{\n+\t\t.name = \"SOCK_DGRAM bidirectional sizes, 64 KiB boundaries and RX refill\",\n+\t\t.run_client = test_dgram_sizes,\n+\t\t.run_server = test_dgram_sizes,\n+\t\t.check = vsock_dgram_check_peer,\n+\t},\n+\t{\n+\t\t.name = \"SOCK_DGRAM connected socket alternates loopback and peer routes\",\n+\t\t.run_client = test_dgram_routes,\n+\t\t.run_server = test_dgram_routes,\n+\t\t.check = vsock_dgram_check_routes,\n+\t},\n+\t{\n+\t\t.name = \"SOCK_DGRAM peer filtering and disconnect across transports\",\n+\t\t.run_client = test_dgram_peer_filter,\n+\t\t.run_server = test_dgram_peer_filter,\n+\t\t.check = vsock_dgram_check_peer,\n+\t},\n+\t{\n+\t\t.name = \"SOCK_DGRAM VMCI ioctl(SIOCINQ) payload length\",\n+\t\t.run_client = test_dgram_vmci_inq,\n+\t\t.run_server = test_dgram_vmci_inq,\n+\t\t.check = vsock_dgram_check_vmci,\n+\t\t.local = true,\n \t},\n \t{},\n };\n@@ -2957,6 +3047,26 @@ static const struct option longopts[] = {\n \t\t.has_arg = required_argument,\n \t\t.val = 't',\n \t},\n+\t{\n+\t\t.name = \"local\",\n+\t\t.has_arg = no_argument,\n+\t\t.val = 'L',\n+\t},\n+\t{\n+\t\t.name = \"bind-cid\",\n+\t\t.has_arg = required_argument,\n+\t\t.val = 'b',\n+\t},\n+\t{\n+\t\t.name = \"to-host\",\n+\t\t.has_arg = no_argument,\n+\t\t.val = 'T',\n+\t},\n+\t{\n+\t\t.name = \"expect-to-host\",\n+\t\t.has_arg = no_argument,\n+\t\t.val = 'E',\n+\t},\n \t{\n \t\t.name = \"help\",\n \t\t.has_arg = no_argument,\n@@ -2972,8 +3082,10 @@ static void usage(void)\n \t\t\"  Server: vsock_test --control-port=1234 --mode=server --peer-cid=3\\n\"\n \t\t\"  Client: vsock_test --control-host=192.168.0.1 --control-port=1234 --mode=client --peer-cid=2\\n\"\n \t\t\"\\n\"\n-\t\t\"Run vsock.ko tests.  Must be launched in both guest\\n\"\n-\t\t\"and host.  One side must use --mode=client and\\n\"\n+\t\t\"  Local: vsock_test --local [--pick=\u003ctest_id\u003e]\\n\"\n+\t\t\"\\n\"\n+\t\t\"Run vsock.ko tests. Paired tests must be launched in both guest\\n\"\n+\t\t\"and host. One side must use --mode=client and\\n\"\n \t\t\"the other side must use --mode=server.\\n\"\n \t\t\"\\n\"\n \t\t\"A TCP control socket connection is used to coordinate tests\\n\"\n@@ -2986,8 +3098,14 @@ static void usage(void)\n \t\t\"specified with --peer-port=\u003cport\u003e (or the default port)\\n\"\n \t\t\"and the next one.\\n\"\n \t\t\"\\n\"\n+\t\t\"--local runs local datagram cases without a control connection.\\n\"\n+\t\t\"\\n\"\n \t\t\"Options:\\n\"\n \t\t\"  --help                 This help message\\n\"\n+\t\t\"  --local                Run local datagram cases without a peer\\n\"\n+\t\t\"  --bind-cid \u003ccid\u003e       Source CID for peer datagram tests [default: ANY]\\n\"\n+\t\t\"  --to-host              Route peer datagrams through the frontend\\n\"\n+\t\t\"  --expect-to-host       Expect the upstream datagram source flag\\n\"\n \t\t\"  --control-host \u003chost\u003e  Server IP address to connect to\\n\"\n \t\t\"  --control-port \u003cport\u003e  Server port to listen on/connect to\\n\"\n \t\t\"  --mode client|server   Server or client mode\\n\"\n@@ -3011,7 +3129,10 @@ int main(int argc, char **argv)\n \t\t.mode = TEST_MODE_UNSET,\n \t\t.peer_cid = VMADDR_CID_ANY,\n \t\t.peer_port = DEFAULT_PEER_PORT,\n+\t\t.bind_cid = VMADDR_CID_ANY,\n \t};\n+\tbool picked = false;\n+\tsize_t i;\n \n \tsrand(time(NULL));\n \tinit_signals();\n@@ -3023,6 +3144,18 @@ int main(int argc, char **argv)\n \t\t\tbreak;\n \n \t\tswitch (opt) {\n+\t\tcase 'L':\n+\t\t\topts.local = true;\n+\t\t\tbreak;\n+\t\tcase 'b':\n+\t\t\topts.bind_cid = parse_cid(optarg);\n+\t\t\tbreak;\n+\t\tcase 'T':\n+\t\t\topts.to_host = true;\n+\t\t\tbreak;\n+\t\tcase 'E':\n+\t\t\topts.expect_to_host = true;\n+\t\t\tbreak;\n \t\tcase 'H':\n \t\t\tcontrol_host = optarg;\n \t\t\tbreak;\n@@ -3055,6 +3188,7 @@ int main(int argc, char **argv)\n \t\tcase 't':\n \t\t\tpick_test(test_cases, ARRAY_SIZE(test_cases) - 1,\n \t\t\t\t  optarg);\n+\t\t\tpicked = true;\n \t\t\tbreak;\n \t\tcase '?':\n \t\tdefault:\n@@ -3062,6 +3196,22 @@ int main(int argc, char **argv)\n \t\t}\n \t}\n \n+\tif (optind != argc)\n+\t\tusage();\n+\tif (opts.local) {\n+\t\tif (opts.mode != TEST_MODE_UNSET || control_host || control_port)\n+\t\t\tusage();\n+\t\tif (picked) {\n+\t\t\tfor (i = 0; test_cases[i].name; i++) {\n+\t\t\t\tif (!test_cases[i].skip \u0026\u0026 !test_cases[i].local) {\n+\t\t\t\t\tfprintf(stderr, \"test %zu requires paired mode\\n\", i);\n+\t\t\t\t\texit(EXIT_FAILURE);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\trun_tests(test_cases, \u0026opts);\n+\t\treturn EXIT_SUCCESS;\n+\t}\n \tif (!control_port)\n \t\tusage();\n \tif (opts.mode == TEST_MODE_UNSET)\ndiff --git a/tools/testing/vsock/vsock_test_dgram.c b/tools/testing/vsock/vsock_test_dgram.c\nnew file mode 100644\nindex 00000000000000..d818f43176dc9f\n--- /dev/null\n+++ b/tools/testing/vsock/vsock_test_dgram.c\n@@ -0,0 +1,809 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/* Datagram feature tests for vsock. */\n+\n+#include \u003cerrno.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003clinux/bpf.h\u003e\n+#include \u003clinux/filter.h\u003e\n+#include \u003clinux/sockios.h\u003e\n+#include \u003cpoll.h\u003e\n+#include \u003cstdarg.h\u003e\n+#include \u003cstdint.h\u003e\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csys/ioctl.h\u003e\n+#include \u003csys/syscall.h\u003e\n+#include \u003ctime.h\u003e\n+#include \u003cunistd.h\u003e\n+\n+#include \"control.h\"\n+#include \"timeout.h\"\n+#include \"vsock_test_dgram.h\"\n+\n+#define MAX_DGRAM (64 * 1024)\n+\n+static void fail(const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tfprintf(stderr, \"\\nFAIL: \");\n+\tva_start(ap, fmt);\n+\tvfprintf(stderr, fmt, ap);\n+\tva_end(ap);\n+\tfputc('\\n', stderr);\n+\texit(EXIT_FAILURE);\n+}\n+\n+static void check(int ret, const char *operation)\n+{\n+\tif (ret \u003c 0)\n+\t\tfail(\"%s: %s\", operation, strerror(errno));\n+}\n+\n+static void expect_error(ssize_t ret, int error, const char *operation)\n+{\n+\tif (ret != -1 || errno != error)\n+\t\tfail(\"%s: expected %s, got %zd (%s)\", operation,\n+\t\t     strerror(error), ret, ret == -1 ? strerror(errno) : \"success\");\n+}\n+\n+static int64_t now_ms(void)\n+{\n+\tstruct timespec ts;\n+\n+\tcheck(clock_gettime(CLOCK_MONOTONIC, \u0026ts), \"clock_gettime\");\n+\treturn (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;\n+}\n+\n+static short poll_fd(int fd, short events, int milliseconds)\n+{\n+\tstruct pollfd pfd = { .fd = fd, .events = events };\n+\tint ret;\n+\n+\tdo {\n+\t\tret = poll(\u0026pfd, 1, milliseconds);\n+\t} while (ret \u003c 0 \u0026\u0026 errno == EINTR);\n+\tcheck(ret, \"poll\");\n+\tif (pfd.revents \u0026 (POLLERR | POLLNVAL))\n+\t\tfail(\"unexpected poll events %#x\", pfd.revents);\n+\treturn pfd.revents;\n+}\n+\n+static void wait_readable(int fd)\n+{\n+\tif (!(poll_fd(fd, POLLIN, TIMEOUT * 1000) \u0026 POLLIN))\n+\t\tfail(\"waiting for socket readability timed out\");\n+}\n+\n+static struct sockaddr_vm address(unsigned int cid, unsigned int port)\n+{\n+\treturn (struct sockaddr_vm) {\n+\t\t.svm_family = AF_VSOCK,\n+\t\t.svm_cid = cid,\n+\t\t.svm_port = port,\n+\t};\n+}\n+\n+static struct sockaddr_vm sock_name(int fd, bool peer)\n+{\n+\tstruct sockaddr_vm addr;\n+\tsocklen_t len = sizeof(addr);\n+\tint ret;\n+\n+\tif (peer)\n+\t\tret = getpeername(fd, (struct sockaddr *)\u0026addr, \u0026len);\n+\telse\n+\t\tret = getsockname(fd, (struct sockaddr *)\u0026addr, \u0026len);\n+\tcheck(ret, peer ? \"getpeername\" : \"getsockname\");\n+\tif (len != sizeof(addr) || addr.svm_family != AF_VSOCK)\n+\t\tfail(\"invalid socket address\");\n+\treturn addr;\n+}\n+\n+static unsigned int local_cid(void)\n+{\n+\tunsigned int cid;\n+\tint fd;\n+\n+\tfd = open(\"/dev/vsock\", O_RDONLY | O_CLOEXEC);\n+\tcheck(fd, \"open /dev/vsock\");\n+\tcheck(ioctl(fd, IOCTL_VM_SOCKETS_GET_LOCAL_CID, \u0026cid), \"get local CID\");\n+\tclose(fd);\n+\tif (cid == VMADDR_CID_ANY || cid \u003c= VMADDR_CID_LOCAL)\n+\t\tfail(\"a virtio or vhost transport is required (local CID %u)\", cid);\n+\treturn cid;\n+}\n+\n+static void bind_fd(int fd, unsigned int cid, unsigned int port)\n+{\n+\tstruct sockaddr_vm addr = address(cid, port);\n+\n+\tcheck(bind(fd, (struct sockaddr *)\u0026addr, sizeof(addr)), \"bind\");\n+}\n+\n+static int dgram_socket(void)\n+{\n+\tint fd = socket(AF_VSOCK, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);\n+\tint rcvbuf = 1024 * 1024;\n+\n+\tcheck(fd, \"socket(AF_VSOCK, SOCK_DGRAM): check transport/feature support\");\n+\tcheck(setsockopt(fd, SOL_SOCKET, SO_RCVBUF, \u0026rcvbuf, sizeof(rcvbuf)), \"SO_RCVBUF\");\n+\treturn fd;\n+}\n+\n+enum dgram_capability {\n+\tDGRAM_SOCKET = 1 \u003c\u003c 0,\n+\tDGRAM_VIRTIO = 1 \u003c\u003c 1,\n+\tDGRAM_VMCI = 1 \u003c\u003c 2,\n+\tDGRAM_LOOPBACK = 1 \u003c\u003c 3,\n+\tDGRAM_PEER = 1 \u003c\u003c 4,\n+};\n+\n+static bool dgram_unavailable(int error)\n+{\n+\treturn error == EAFNOSUPPORT || error == EPROTONOSUPPORT ||\n+\t       error == ESOCKTNOSUPPORT || error == ENODEV;\n+}\n+\n+static unsigned int dgram_capabilities(const struct test_opts *opts,\n+\t\t\t\t       unsigned int needs)\n+{\n+\tunsigned int caps = DGRAM_SOCKET;\n+\tstruct sockaddr_vm peer;\n+\tint fd, local_fd;\n+\n+\tfd = socket(AF_VSOCK, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);\n+\tif (fd \u003c 0) {\n+\t\tif (dgram_unavailable(errno))\n+\t\t\treturn 0;\n+\t\tcheck(fd, \"probe datagram socket\");\n+\t}\n+\t/* VMCI registers the dedicated datagram transport when loaded. */\n+\tif (!access(\"/sys/module/vmw_vsock_vmci_transport\", F_OK))\n+\t\tcaps |= DGRAM_VMCI;\n+\telse\n+\t\tcaps |= DGRAM_VIRTIO;\n+\n+\tif ((needs \u0026 DGRAM_PEER) \u0026\u0026 (caps \u0026 DGRAM_VIRTIO)) {\n+\t\tbind_fd(fd, opts-\u003ebind_cid, VMADDR_PORT_ANY);\n+\t\tpeer = address(opts-\u003epeer_cid, opts-\u003epeer_port);\n+\t\tif (opts-\u003eto_host)\n+\t\t\tpeer.svm_flags = VMADDR_FLAG_TO_HOST;\n+\t\tif (!connect(fd, (struct sockaddr *)\u0026peer, sizeof(peer)))\n+\t\t\tcaps |= DGRAM_PEER;\n+\t\telse if (!dgram_unavailable(errno) \u0026\u0026 errno != EINVAL)\n+\t\t\tcheck(-1, \"probe peer datagram route\");\n+\t}\n+\tclose(fd);\n+\n+\tif ((needs \u0026 DGRAM_LOOPBACK) \u0026\u0026 (caps \u0026 DGRAM_VIRTIO)) {\n+\t\t/* Binding a stream socket to CID_LOCAL probes registration of\n+\t\t * the loopback transport without sending a packet to the host.\n+\t\t */\n+\t\tlocal_fd = vsock_bind_try(VMADDR_CID_LOCAL, VMADDR_PORT_ANY, SOCK_STREAM);\n+\t\tif (local_fd \u003e= 0) {\n+\t\t\tcaps |= DGRAM_LOOPBACK;\n+\t\t\tclose(local_fd);\n+\t\t} else if (errno != EADDRNOTAVAIL) {\n+\t\t\tcheck(local_fd, \"probe loopback transport\");\n+\t\t}\n+\t}\n+\treturn caps;\n+}\n+\n+static const char *dgram_check(const struct test_opts *opts, unsigned int needs)\n+{\n+\tunsigned int local = dgram_capabilities(opts, needs);\n+\tunsigned int both = local;\n+\n+\t/* run_tests() has already synchronized explicit --pick/--skip\n+\t * selection. Both peers can now safely exchange capabilities.\n+\t */\n+\tif (!opts-\u003elocal) {\n+\t\tcontrol_writeulong(local);\n+\t\tboth \u0026= control_readulong();\n+\t}\n+\tif (!(both \u0026 DGRAM_SOCKET))\n+\t\treturn local \u0026 DGRAM_SOCKET ?\n+\t\t\t\"datagrams unavailable at peer\" : \"datagrams unavailable locally\";\n+\tif ((needs \u0026 DGRAM_VIRTIO) \u0026\u0026 !(both \u0026 DGRAM_VIRTIO))\n+\t\treturn \"requires virtio/vhost datagram transports on both peers\";\n+\tif ((needs \u0026 DGRAM_VMCI) \u0026\u0026 !(both \u0026 DGRAM_VMCI))\n+\t\treturn \"requires VMCI datagram transports on both peers\";\n+\tif ((needs \u0026 DGRAM_LOOPBACK) \u0026\u0026 !(both \u0026 DGRAM_LOOPBACK))\n+\t\treturn \"load vsock_loopback on both peers\";\n+\tif ((needs \u0026 DGRAM_PEER) \u0026\u0026 !(both \u0026 DGRAM_PEER))\n+\t\treturn \"peer route does not support datagrams\";\n+\treturn NULL;\n+}\n+\n+const char *vsock_dgram_check_local(const struct test_opts *opts)\n+{\n+\treturn dgram_check(opts, DGRAM_VIRTIO | DGRAM_LOOPBACK);\n+}\n+\n+const char *vsock_dgram_check_peer(const struct test_opts *opts)\n+{\n+\treturn dgram_check(opts, DGRAM_VIRTIO | DGRAM_PEER);\n+}\n+\n+const char *vsock_dgram_check_routes(const struct test_opts *opts)\n+{\n+\treturn dgram_check(opts, DGRAM_VIRTIO | DGRAM_PEER | DGRAM_LOOPBACK);\n+}\n+\n+const char *vsock_dgram_check_vmci(const struct test_opts *opts)\n+{\n+\treturn dgram_check(opts, DGRAM_VMCI);\n+}\n+\n+static int bound_socket(unsigned int cid)\n+{\n+\tint fd = dgram_socket();\n+\n+\tbind_fd(fd, cid, VMADDR_PORT_ANY);\n+\treturn fd;\n+}\n+\n+static struct sockaddr_vm local_address(int fd)\n+{\n+\tstruct sockaddr_vm addr = sock_name(fd, false);\n+\n+\taddr.svm_cid = VMADDR_CID_LOCAL;\n+\treturn addr;\n+}\n+\n+static void connect_fd(int fd, const struct sockaddr_vm *addr)\n+{\n+\tcheck(connect(fd, (const struct sockaddr *)addr, sizeof(*addr)), \"connect\");\n+}\n+\n+static void disconnect_fd(int fd)\n+{\n+\tstruct sockaddr_vm addr = { .svm_family = AF_UNSPEC };\n+\tsocklen_t len = sizeof(addr);\n+\n+\tcheck(connect(fd, (struct sockaddr *)\u0026addr, sizeof(addr)), \"disconnect\");\n+\texpect_error(getpeername(fd, (struct sockaddr *)\u0026addr, \u0026len),\n+\t\t     ENOTCONN, \"getpeername after disconnect\");\n+}\n+\n+static int queue_bytes(int fd, unsigned long op)\n+{\n+\tint bytes;\n+\n+\tcheck(ioctl(fd, op, \u0026bytes), \"queue ioctl\");\n+\tif (bytes \u003c 0)\n+\t\tfail(\"negative queue length: %d\", bytes);\n+\treturn bytes;\n+}\n+\n+static void expect_inq(int fd, int expected)\n+{\n+\tint actual = queue_bytes(fd, SIOCINQ);\n+\n+\tif (actual != expected)\n+\t\tfail(\"SIOCINQ: expected %d, got %d\", expected, actual);\n+}\n+\n+static void wait_sent(int fd)\n+{\n+\tint64_t deadline = now_ms() + TIMEOUT * 1000;\n+\n+\twhile (queue_bytes(fd, SIOCOUTQ)) {\n+\t\tif (now_ms() \u003e= deadline)\n+\t\t\tfail(\"SIOCOUTQ did not return to zero\");\n+\t\tusleep(1000);\n+\t}\n+}\n+\n+static void send_packet(int fd, const struct sockaddr_vm *addr,\n+\t\t\tconst void *buf, size_t len)\n+{\n+\tint64_t deadline = now_ms() + TIMEOUT * 1000;\n+\tssize_t ret;\n+\n+\tfor (;;) {\n+\t\tret = sendto(fd, buf, len, MSG_DONTWAIT | MSG_NOSIGNAL,\n+\t\t\t     (const struct sockaddr *)addr, addr ? sizeof(*addr) : 0);\n+\t\tif (ret \u003e= 0)\n+\t\t\tbreak;\n+\t\tif (errno != EAGAIN \u0026\u0026 errno != EINTR)\n+\t\t\tfail(\"sendto(%zu bytes): %s\", len, strerror(errno));\n+\t\tif (now_ms() \u003e= deadline)\n+\t\t\tfail(\"sendto(%zu bytes) timed out\", len);\n+\t\tpoll_fd(fd, POLLOUT, 1);\n+\t}\n+\tif (ret != len)\n+\t\tfail(\"partial datagram send: %zd/%zu\", ret, len);\n+}\n+\n+static struct sockaddr_vm recv_packet(int fd, void *buf, size_t capacity,\n+\t\t\t\t      int flags, size_t expected, bool truncated)\n+{\n+\tstruct sockaddr_vm src = { 0 };\n+\tstruct iovec iov = { .iov_base = buf, .iov_len = capacity };\n+\tstruct msghdr msg = {\n+\t\t.msg_name = \u0026src,\n+\t\t.msg_namelen = sizeof(src),\n+\t\t.msg_iov = \u0026iov,\n+\t\t.msg_iovlen = 1,\n+\t};\n+\tssize_t ret;\n+\n+\twait_readable(fd);\n+\tret = recvmsg(fd, \u0026msg, flags | MSG_DONTWAIT);\n+\tcheck(ret, \"recvmsg\");\n+\tif (ret != expected || !!(msg.msg_flags \u0026 MSG_TRUNC) != truncated)\n+\t\tfail(\"recvmsg: expected %zu bytes, trunc=%d; got %zd, flags=%#x\",\n+\t\t     expected, truncated, ret, msg.msg_flags);\n+\tif (msg.msg_namelen != sizeof(src) || src.svm_family != AF_VSOCK)\n+\t\tfail(\"invalid recvmsg source address\");\n+\treturn src;\n+}\n+\n+static void expect_empty(int fd)\n+{\n+\tchar byte;\n+\n+\tif (poll_fd(fd, POLLIN, 0) \u0026 POLLIN)\n+\t\tfail(\"unexpected queued datagram\");\n+\texpect_error(recv(fd, \u0026byte, 1, MSG_DONTWAIT), EAGAIN, \"empty recv\");\n+\texpect_inq(fd, 0);\n+}\n+\n+void test_dgram_bind(const struct test_opts *opts)\n+{\n+\tint rx = bound_socket(VMADDR_CID_ANY);\n+\tstruct sockaddr_vm dest = local_address(rx);\n+\tint tx = dgram_socket();\n+\tint other = dgram_socket();\n+\tstruct sockaddr_vm src;\n+\tchar byte;\n+\n+\tdest.svm_cid = VMADDR_CID_ANY;\n+\texpect_error(bind(other, (struct sockaddr *)\u0026dest, sizeof(dest)),\n+\t\t     EADDRINUSE, \"duplicate bind\");\n+\tsend_packet(tx, \u0026dest, \"A\", 1);\n+\tsrc = recv_packet(rx, \u0026byte, 1, 0, 1, false);\n+\tif (byte != 'A' || src.svm_port != sock_name(tx, false).svm_port ||\n+\t    src.svm_cid != VMADDR_CID_LOCAL || src.svm_port \u003c 1024)\n+\t\tfail(\"autobind or wildcard destination returned an invalid source\");\n+\tclose(rx);\n+\tbind_fd(other, VMADDR_CID_ANY, dest.svm_port);\n+\tclose(other);\n+\tclose(tx);\n+}\n+\n+void test_dgram_receive(const struct test_opts *opts)\n+{\n+\tint rx = bound_socket(VMADDR_CID_ANY);\n+\tint tx = bound_socket(VMADDR_CID_ANY);\n+\tstruct sockaddr_vm dest = local_address(rx);\n+\tchar buf[80];\n+\tchar first[31], second[79];\n+\n+\tmemset(first, 'A', sizeof(first));\n+\tmemset(second, 'B', sizeof(second));\n+\texpect_empty(rx);\n+\tif (!(poll_fd(tx, POLLOUT, 0) \u0026 POLLOUT))\n+\t\tfail(\"new socket is not writable\");\n+\tif (queue_bytes(tx, SIOCOUTQ))\n+\t\tfail(\"new socket has pending output\");\n+\tsend_packet(tx, \u0026dest, first, sizeof(first));\n+\tsend_packet(tx, \u0026dest, second, sizeof(second));\n+\twait_readable(rx);\n+\texpect_inq(rx, sizeof(first));\n+\trecv_packet(rx, buf, 5, MSG_PEEK, 5, true);\n+\tif (memcmp(buf, first, 5))\n+\t\tfail(\"MSG_PEEK payload mismatch\");\n+\texpect_inq(rx, sizeof(first));\n+\trecv_packet(rx, buf, 5, MSG_TRUNC, sizeof(first), true);\n+\twait_readable(rx);\n+\texpect_inq(rx, sizeof(second));\n+\trecv_packet(rx, buf, sizeof(buf), 0, sizeof(second), false);\n+\tif (memcmp(buf, second, sizeof(second)))\n+\t\tfail(\"datagram boundary or payload mismatch\");\n+\tsend_packet(tx, \u0026dest, \"\", 0);\n+\twait_readable(rx);\n+\texpect_inq(rx, 0);\n+\trecv_packet(rx, buf, sizeof(buf), MSG_PEEK, 0, false);\n+\trecv_packet(rx, buf, sizeof(buf), 0, 0, false);\n+\tsend_packet(tx, \u0026dest, first, sizeof(first));\n+\trecv_packet(rx, buf, 0, MSG_TRUNC, sizeof(first), true);\n+\texpect_empty(rx);\n+\twait_sent(tx);\n+\tclose(tx);\n+\tclose(rx);\n+}\n+\n+void test_dgram_receive_flags(const struct test_opts *opts)\n+{\n+\tint rx = bound_socket(VMADDR_CID_ANY);\n+\tint tx = bound_socket(VMADDR_CID_ANY);\n+\tstruct sockaddr_vm dest = local_address(rx);\n+\tconst int flags[] = { MSG_OOB, MSG_ERRQUEUE };\n+\tchar byte;\n+\tsize_t i;\n+\n+\tsend_packet(tx, \u0026dest, \"A\", 1);\n+\twait_readable(rx);\n+\tfor (i = 0; i \u003c ARRAY_SIZE(flags); i++) {\n+\t\texpect_error(recv(rx, \u0026byte, 1, flags[i] | MSG_DONTWAIT),\n+\t\t\t     EOPNOTSUPP, \"unsupported receive flag\");\n+\t\texpect_inq(rx, 1);\n+\t}\n+\trecv_packet(rx, \u0026byte, 1, 0, 1, false);\n+\tif (byte != 'A')\n+\t\tfail(\"unsupported flags consumed the normal packet\");\n+\tclose(tx);\n+\tclose(rx);\n+}\n+\n+void test_dgram_vmci_inq(const struct test_opts *opts)\n+{\n+\tint rx = bound_socket(VMADDR_CID_ANY);\n+\tint tx = bound_socket(VMADDR_CID_ANY);\n+\tstruct sockaddr_vm dest = sock_name(rx, false);\n+\tchar first[31], second[79], buf[80];\n+\n+\tmemset(first, 'A', sizeof(first));\n+\tmemset(second, 'B', sizeof(second));\n+\t/* CID_ANY is resolved by the dedicated transport to its local CID. */\n+\tdest.svm_cid = VMADDR_CID_ANY;\n+\tsend_packet(tx, \u0026dest, first, sizeof(first));\n+\tsend_packet(tx, \u0026dest, second, sizeof(second));\n+\twait_readable(rx);\n+\texpect_inq(rx, sizeof(first));\n+\trecv_packet(rx, buf, sizeof(buf), 0, sizeof(first), false);\n+\tif (memcmp(buf, first, sizeof(first)))\n+\t\tfail(\"VMCI first payload mismatch\");\n+\twait_readable(rx);\n+\texpect_inq(rx, sizeof(second));\n+\trecv_packet(rx, buf, sizeof(buf), 0, sizeof(second), false);\n+\tif (memcmp(buf, second, sizeof(second)))\n+\t\tfail(\"VMCI second payload mismatch\");\n+\tsend_packet(tx, \u0026dest, \"\", 0);\n+\twait_readable(rx);\n+\texpect_inq(rx, 0);\n+\trecv_packet(rx, buf, sizeof(buf), 0, 0, false);\n+\texpect_empty(rx);\n+\tclose(tx);\n+\tclose(rx);\n+}\n+\n+void test_dgram_filter(const struct test_opts *opts)\n+{\n+\tstruct sock_filter insn[] = { BPF_STMT(BPF_RET | BPF_K, 7) };\n+\tstruct sock_fprog prog = { .len = ARRAY_SIZE(insn), .filter = insn };\n+\tint rx = bound_socket(VMADDR_CID_ANY);\n+\tint tx = bound_socket(VMADDR_CID_ANY);\n+\tstruct sockaddr_vm dest = local_address(rx);\n+\tchar data[100], buf[100];\n+\n+\tmemset(data, 'F', sizeof(data));\n+\tcheck(setsockopt(rx, SOL_SOCKET, SO_ATTACH_FILTER, \u0026prog, sizeof(prog)),\n+\t      \"SO_ATTACH_FILTER\");\n+\tsend_packet(tx, \u0026dest, data, sizeof(data));\n+\twait_readable(rx);\n+\texpect_inq(rx, 7);\n+\trecv_packet(rx, buf, 1, MSG_PEEK | MSG_TRUNC, 7, true);\n+\texpect_inq(rx, 7);\n+\tmemset(buf, 0, sizeof(buf));\n+\trecv_packet(rx, buf, sizeof(buf), MSG_TRUNC, 7, false);\n+\tif (memcmp(buf, data, 7) || buf[7])\n+\t\tfail(\"filtered receive copied beyond the filtered payload\");\n+\tsend_packet(tx, \u0026dest, data, sizeof(data));\n+\trecv_packet(rx, buf, 1, MSG_TRUNC, 7, true);\n+\texpect_empty(rx);\n+\tclose(tx);\n+\tclose(rx);\n+}\n+\n+static void check_peer_filter(int rx, int allowed, int stray)\n+{\n+\tstruct sockaddr_vm dest = local_address(rx);\n+\tchar byte;\n+\n+\t/* Both packets use the same ordered loopback worker. */\n+\tsend_packet(stray, \u0026dest, \"B\", 1);\n+\tsend_packet(allowed, \u0026dest, \"A\", 1);\n+\trecv_packet(rx, \u0026byte, 1, 0, 1, false);\n+\tif (byte != 'A')\n+\t\tfail(\"connected socket accepted a different peer\");\n+\texpect_empty(rx);\n+}\n+\n+void test_dgram_connect(const struct test_opts *opts)\n+{\n+\tint rx = bound_socket(VMADDR_CID_ANY);\n+\tint allowed = bound_socket(VMADDR_CID_ANY);\n+\tint stray = bound_socket(VMADDR_CID_ANY);\n+\tstruct sockaddr_vm peer = local_address(allowed);\n+\tstruct sockaddr_vm dest = local_address(rx);\n+\tchar byte;\n+\n+\tconnect_fd(rx, \u0026peer);\n+\tcheck_peer_filter(rx, allowed, stray);\n+\tdisconnect_fd(rx);\n+\tsend_packet(stray, \u0026dest, \"B\", 1);\n+\trecv_packet(rx, \u0026byte, 1, 0, 1, false);\n+\tif (byte != 'B')\n+\t\tfail(\"disconnect did not remove peer filtering\");\n+\tconnect_fd(rx, \u0026peer);\n+\tcheck(shutdown(rx, SHUT_WR), \"shutdown SHUT_WR\");\n+\tif (poll_fd(rx, POLLOUT, 0) \u0026 POLLOUT)\n+\t\tfail(\"write-shutdown socket is still writable\");\n+\tcheck_peer_filter(rx, allowed, stray);\n+\tcheck(shutdown(rx, SHUT_RD), \"shutdown SHUT_RD\");\n+\tif (!(poll_fd(rx, POLLIN | POLLRDHUP, 0) \u0026 POLLRDHUP))\n+\t\tfail(\"read shutdown did not report POLLRDHUP\");\n+\tclose(stray);\n+\tclose(allowed);\n+\tclose(rx);\n+}\n+\n+void test_dgram_source_cid(const struct test_opts *opts)\n+{\n+\tunsigned int cids[] = { VMADDR_CID_LOCAL, local_cid() };\n+\tsize_t i;\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(cids); i++) {\n+\t\tint rx = bound_socket(VMADDR_CID_ANY);\n+\t\tint tx = bound_socket(cids[i]);\n+\t\tstruct sockaddr_vm dest = local_address(rx);\n+\t\tstruct sockaddr_vm src;\n+\t\tchar byte;\n+\n+\t\tsend_packet(tx, \u0026dest, \"A\", 1);\n+\t\tsrc = recv_packet(rx, \u0026byte, 1, 0, 1, false);\n+\t\tif (src.svm_cid != cids[i] || src.svm_flags ||\n+\t\t    src.svm_port != sock_name(tx, false).svm_port)\n+\t\t\tfail(\"explicit loopback source CID %u was not preserved\", cids[i]);\n+\t\tsend_packet(rx, \u0026src, \"R\", 1);\n+\t\trecv_packet(tx, \u0026byte, 1, 0, 1, false);\n+\t\tif (byte != 'R')\n+\t\t\tfail(\"reply to explicit source CID failed\");\n+\t\tclose(tx);\n+\t\tclose(rx);\n+\t}\n+}\n+\n+void test_dgram_bound_lookup(const struct test_opts *opts)\n+{\n+\tconst int types[] = { SOCK_STREAM, SOCK_SEQPACKET };\n+\tunsigned int cid = local_cid();\n+\tsize_t i;\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(types); i++) {\n+\t\tint listener = vsock_bind(VMADDR_CID_ANY, VMADDR_PORT_ANY, types[i]);\n+\t\tint stream = vsock_bind(VMADDR_CID_LOCAL, VMADDR_PORT_ANY, types[i]);\n+\t\tstruct sockaddr_vm listen_addr = local_address(listener);\n+\t\tstruct sockaddr_vm src = sock_name(stream, false);\n+\t\tstruct sockaddr_vm dest;\n+\t\tint accepted, tx, rx;\n+\t\tchar byte;\n+\n+\t\tcheck(listen(listener, 1), \"listen\");\n+\t\tif (vsock_connect_fd(stream, listen_addr.svm_cid, listen_addr.svm_port))\n+\t\t\tfail(\"%s loopback connect: %s\",\n+\t\t\t     types[i] == SOCK_STREAM ? \"stream\" : \"seqpacket\", strerror(errno));\n+\t\twait_readable(listener);\n+\t\taccepted = accept4(listener, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);\n+\t\tcheck(accepted, \"accept\");\n+\t\tif (sock_name(accepted, true).svm_cid != VMADDR_CID_LOCAL ||\n+\t\t    sock_name(accepted, true).svm_port != src.svm_port)\n+\t\t\tfail(\"unexpected accepted loopback peer\");\n+\t\tclose(listener);\n+\t\trx = dgram_socket();\n+\t\tbind_fd(rx, cid, src.svm_port);\n+\t\tdest = sock_name(rx, false);\n+\t\ttx = dgram_socket();\n+\t\t/* Reuse the listener's port for the datagram source. The receive\n+\t\t * socket uses the stream client's port at another local CID.\n+\t\t * Connected lookup ignores the destination CID, so it would find\n+\t\t * the stream client before the bound datagram socket.\n+\t\t */\n+\t\tbind_fd(tx, VMADDR_CID_LOCAL, listen_addr.svm_port);\n+\t\tsend_packet(tx, \u0026dest, \"D\", 1);\n+\t\trecv_packet(rx, \u0026byte, 1, 0, 1, false);\n+\t\tif (byte != 'D' || (poll_fd(stream, POLLIN, 0) \u0026 POLLIN) ||\n+\t\t    (poll_fd(accepted, POLLIN, 0) \u0026 POLLIN))\n+\t\t\tfail(\"connected socket shadowed the reused datagram port\");\n+\t\tsend_byte(stream, 1, 0);\n+\t\twait_readable(accepted);\n+\t\trecv_byte(accepted, 1, 0);\n+\t\tsend_byte(accepted, 1, 0);\n+\t\twait_readable(stream);\n+\t\trecv_byte(stream, 1, 0);\n+\t\tclose(tx);\n+\t\tclose(rx);\n+\t\tclose(accepted);\n+\t\tclose(stream);\n+\t}\n+}\n+\n+void test_dgram_sockmap(const struct test_opts *opts)\n+{\n+\tconst int types[] = { BPF_MAP_TYPE_SOCKMAP, BPF_MAP_TYPE_SOCKHASH };\n+\tint fd = bound_socket(VMADDR_CID_ANY);\n+\tstruct sockaddr_vm addr = local_address(fd);\n+\tuint64_t value = fd;\n+\tuint32_t key = 0;\n+\tsize_t i;\n+\n+\tconnect_fd(fd, \u0026addr);\n+\tfor (i = 0; i \u003c ARRAY_SIZE(types); i++) {\n+\t\tunion bpf_attr attr = {\n+\t\t\t.map_type = types[i],\n+\t\t\t.key_size = sizeof(key),\n+\t\t\t.value_size = sizeof(value),\n+\t\t\t.max_entries = 1,\n+\t\t};\n+\t\tint map = syscall(__NR_bpf, BPF_MAP_CREATE, \u0026attr, sizeof(attr));\n+\n+\t\tcheck(map, \"BPF_MAP_CREATE (requires BPF privileges and CONFIG_BPF_SYSCALL)\");\n+\t\tmemset(\u0026attr, 0, sizeof(attr));\n+\t\tattr.map_fd = map;\n+\t\tattr.key = (uintptr_t)\u0026key;\n+\t\tattr.value = (uintptr_t)\u0026value;\n+\t\tattr.flags = BPF_ANY;\n+\t\texpect_error(syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, \u0026attr, sizeof(attr)),\n+\t\t\t     EOPNOTSUPP, \"datagram sockmap admission\");\n+\t\tclose(map);\n+\t}\n+\tclose(fd);\n+}\n+\n+void test_dgram_nested_source(const struct test_opts *opts)\n+{\n+\tint rx, tx;\n+\tstruct sockaddr_vm dest;\n+\n+\tif (local_cid() \u003c= VMADDR_CID_HOST)\n+\t\tfail(\"this test requires both a guest frontend and vhost_vsock\");\n+\trx = bound_socket(VMADDR_CID_LOCAL);\n+\ttx = bound_socket(VMADDR_CID_HOST);\n+\tdest = local_address(rx);\n+\texpect_error(sendto(tx, \"A\", 1, MSG_NOSIGNAL,\n+\t\t\t    (struct sockaddr *)\u0026dest, sizeof(dest)),\n+\t\t     EADDRNOTAVAIL, \"nested CID_HOST loopback send\");\n+\texpect_error(connect(tx, (struct sockaddr *)\u0026dest, sizeof(dest)),\n+\t\t     EADDRNOTAVAIL, \"nested CID_HOST loopback connect\");\n+\tif (queue_bytes(tx, SIOCOUTQ))\n+\t\tfail(\"rejected route left pending output\");\n+\texpect_empty(rx);\n+\tclose(tx);\n+\tclose(rx);\n+}\n+\n+static int peer_socket(const struct test_opts *opts, struct sockaddr_vm *peer)\n+{\n+\tint fd = bound_socket(opts-\u003ebind_cid);\n+\n+\tcontrol_writeulong(sock_name(fd, false).svm_port);\n+\t*peer = address(opts-\u003epeer_cid, control_readulong());\n+\tif (opts-\u003eto_host)\n+\t\tpeer-\u003esvm_flags = VMADDR_FLAG_TO_HOST;\n+\treturn fd;\n+}\n+\n+static void check_source(const struct test_opts *opts,\n+\t\t\t const struct sockaddr_vm *peer, const struct sockaddr_vm *src)\n+{\n+\tbool upstream = opts-\u003eexpect_to_host || opts-\u003epeer_cid == VMADDR_CID_HOST;\n+\n+\tif (src-\u003esvm_cid != peer-\u003esvm_cid || src-\u003esvm_port != peer-\u003esvm_port ||\n+\t    src-\u003esvm_flags != (upstream ? VMADDR_FLAG_TO_HOST : 0))\n+\t\tfail(\"source expected %u:%u flags=%u, got %u:%u flags=%u\",\n+\t\t     peer-\u003esvm_cid, peer-\u003esvm_port, upstream ? VMADDR_FLAG_TO_HOST : 0,\n+\t\t     src-\u003esvm_cid, src-\u003esvm_port, src-\u003esvm_flags);\n+}\n+\n+static void fill_payload(char *buf, size_t len, unsigned int seed)\n+{\n+\tsize_t i;\n+\n+\tfor (i = 0; i \u003c len; i++)\n+\t\tbuf[i] = (i * 131 + seed) \u0026 0xff;\n+}\n+\n+void test_dgram_sizes(const struct test_opts *opts)\n+{\n+\tconst size_t sizes[] = { 0, 1, 7, 255, 4095, 4096, 4097,\n+\t\t\t\t8192, 32768, 65535, MAX_DGRAM };\n+\tchar *data = malloc(MAX_DGRAM + 1);\n+\tchar *buf = malloc(MAX_DGRAM + 1);\n+\tstruct sockaddr_vm peer, src;\n+\tunsigned int round;\n+\tint fd;\n+\tsize_t i;\n+\n+\tif (!data || !buf)\n+\t\tfail(\"malloc\");\n+\tfd = peer_socket(opts, \u0026peer);\n+\tfill_payload(data, MAX_DGRAM + 1, 0);\n+\texpect_error(sendto(fd, data, MAX_DGRAM + 1, MSG_NOSIGNAL,\n+\t\t\t    (struct sockaddr *)\u0026peer, sizeof(peer)),\n+\t\t     EMSGSIZE, \"oversized datagram\");\n+\tif (queue_bytes(fd, SIOCOUTQ))\n+\t\tfail(\"oversized datagram left pending output\");\n+\t/* More than a default RX ring's worth, including on small rings. */\n+\tfor (round = 0; round \u003c 32; round++) {\n+\t\tfor (i = 0; i \u003c ARRAY_SIZE(sizes); i++) {\n+\t\t\tfill_payload(data, sizes[i], round * ARRAY_SIZE(sizes) + i);\n+\t\t\tif (opts-\u003emode == TEST_MODE_CLIENT)\n+\t\t\t\tsend_packet(fd, \u0026peer, data, sizes[i]);\n+\t\t\tsrc = recv_packet(fd, buf, MAX_DGRAM + 1, 0, sizes[i], false);\n+\t\t\tcheck_source(opts, \u0026peer, \u0026src);\n+\t\t\tif (memcmp(data, buf, sizes[i]))\n+\t\t\t\tfail(\"payload mismatch: size %zu, round %u\", sizes[i], round);\n+\t\t\tif (opts-\u003emode == TEST_MODE_SERVER)\n+\t\t\t\tsend_packet(fd, \u0026src, buf, sizes[i]);\n+\t\t}\n+\t}\n+\twait_sent(fd);\n+\texpect_empty(fd);\n+\tclose(fd);\n+\tfree(buf);\n+\tfree(data);\n+}\n+\n+void test_dgram_routes(const struct test_opts *opts)\n+{\n+\tstruct sockaddr_vm peer, src, stored, local;\n+\tint fd = peer_socket(opts, \u0026peer);\n+\tint aux = bound_socket(VMADDR_CID_ANY);\n+\tunsigned int round;\n+\tchar byte;\n+\n+\tconnect_fd(fd, \u0026peer);\n+\tstored = sock_name(fd, true);\n+\tlocal = local_address(aux);\n+\tfor (round = 0; round \u003c 32; round++) {\n+\t\tsend_packet(fd, \u0026local, \"L\", 1);\n+\t\trecv_packet(aux, \u0026byte, 1, 0, 1, false);\n+\t\tif (byte != 'L')\n+\t\t\tfail(\"sendto on a connected socket did not use loopback\");\n+\t\tsrc = sock_name(fd, true);\n+\t\tif (src.svm_cid != stored.svm_cid || src.svm_port != stored.svm_port ||\n+\t\t    src.svm_flags != stored.svm_flags)\n+\t\t\tfail(\"per-message route changed the connected peer\");\n+\t\tsend_packet(fd, NULL, \"P\", 1);\n+\t\tsrc = recv_packet(fd, \u0026byte, 1, 0, 1, false);\n+\t\tcheck_source(opts, \u0026peer, \u0026src);\n+\t\tif (byte != 'P')\n+\t\t\tfail(\"connected send did not retain the peer route\");\n+\t}\n+\twait_sent(fd);\n+\tclose(aux);\n+\tclose(fd);\n+}\n+\n+void test_dgram_peer_filter(const struct test_opts *opts)\n+{\n+\tstruct sockaddr_vm peer, src;\n+\tint fd = peer_socket(opts, \u0026peer);\n+\tint stray = bound_socket(opts-\u003ebind_cid);\n+\tchar byte;\n+\n+\tconnect_fd(fd, \u0026peer);\n+\tcontrol_writeln(\"CONNECTED\");\n+\tcontrol_expectln(\"CONNECTED\");\n+\tsend_packet(stray, \u0026peer, \"B\", 1);\n+\tsend_packet(fd, NULL, \"A\", 1);\n+\tsrc = recv_packet(fd, \u0026byte, 1, 0, 1, false);\n+\tcheck_source(opts, \u0026peer, \u0026src);\n+\tif (byte != 'A')\n+\t\tfail(\"connected socket accepted a different peer port\");\n+\tdisconnect_fd(fd);\n+\tcontrol_writeln(\"DISCONNECTED\");\n+\tcontrol_expectln(\"DISCONNECTED\");\n+\tsend_packet(stray, \u0026peer, \"B\", 1);\n+\tsrc = recv_packet(fd, \u0026byte, 1, 0, 1, false);\n+\tif (byte != 'B' || src.svm_cid != peer.svm_cid || src.svm_port == peer.svm_port)\n+\t\tfail(\"disconnected socket did not accept the new peer\");\n+\tclose(stray);\n+\tclose(fd);\n+}\ndiff --git a/tools/testing/vsock/vsock_test_dgram.h b/tools/testing/vsock/vsock_test_dgram.h\nnew file mode 100644\nindex 00000000000000..0dec74ef5f4685\n--- /dev/null\n+++ b/tools/testing/vsock/vsock_test_dgram.h\n@@ -0,0 +1,26 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+#ifndef VSOCK_TEST_DGRAM_H\n+#define VSOCK_TEST_DGRAM_H\n+\n+#include \"util.h\"\n+\n+const char *vsock_dgram_check_local(const struct test_opts *opts);\n+const char *vsock_dgram_check_peer(const struct test_opts *opts);\n+const char *vsock_dgram_check_routes(const struct test_opts *opts);\n+const char *vsock_dgram_check_vmci(const struct test_opts *opts);\n+\n+void test_dgram_bind(const struct test_opts *opts);\n+void test_dgram_receive(const struct test_opts *opts);\n+void test_dgram_receive_flags(const struct test_opts *opts);\n+void test_dgram_filter(const struct test_opts *opts);\n+void test_dgram_connect(const struct test_opts *opts);\n+void test_dgram_source_cid(const struct test_opts *opts);\n+void test_dgram_bound_lookup(const struct test_opts *opts);\n+void test_dgram_sockmap(const struct test_opts *opts);\n+void test_dgram_nested_source(const struct test_opts *opts);\n+void test_dgram_sizes(const struct test_opts *opts);\n+void test_dgram_routes(const struct test_opts *opts);\n+void test_dgram_peer_filter(const struct test_opts *opts);\n+void test_dgram_vmci_inq(const struct test_opts *opts);\n+\n+#endif /* VSOCK_TEST_DGRAM_H */\n"
}

2/1 2026/10/29 04:41 agent patch-evaluator
1m Model:
core

Args:
null

Results:
{
  "EnableConfigs": [
    "VSOCKETS",
    "VSOCKETS_LOOPBACK",
    "VIRTIO_VSOCKETS",
    "VHOST_VSOCK"
  ],
  "FocusSymbols": [
    "vsock_dgram_get_transport",
    "vsock_bind_common",
    "virtio_transport_dgram_enqueue",
    "virtio_transport_dgram_dequeue",
    "virtio_transport_recv_pkt",
    "vhost_transport_do_send_pkt",
    "vsock_dgram_sendmsg",
    "vsock_dgram_connect"
  ],
  "Reasoning": "The patch series adds SOCK_DGRAM datagram support to virtio-vsock, vhost-vsock, and vsock loopback transports. It introduces new datagram transport selection logic (vsock_dgram_get_transport), refactors port binding (vsock_bind_common), implements datagram queuing and dequeuing (virtio_transport_dgram_enqueue, virtio_transport_dgram_dequeue), handles datagram packet receipt (virtio_transport_recv_pkt), and modifies vhost descriptor processing (vhost_transport_do_send_pkt). AF_VSOCK and virtio/vhost devices are standard networking facilities accessible in virtualized environments, making these changes well worth fuzzing.",
  "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 827c224662592a09246125151432c72c4a814d76
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Oct 29 04:41:17 2026 +0000

    syz-cluster: applied patch under review

diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
index 0cb87971d74919..2294ed8d30a84b 100644
--- a/drivers/vhost/vsock.c
+++ b/drivers/vhost/vsock.c
@@ -32,7 +32,8 @@
 static const int vhost_vsock_bits[] = {
 	VHOST_FEATURES,
 	VIRTIO_F_ACCESS_PLATFORM,
-	VIRTIO_VSOCK_F_SEQPACKET
+	VIRTIO_VSOCK_F_SEQPACKET,
+	VIRTIO_VSOCK_F_DGRAM,
 };
 
 #define VHOST_VSOCK_FEATURES VHOST_FEATURES_U64(vhost_vsock_bits, 0)
@@ -62,6 +63,7 @@ struct vhost_vsock {
 	u32 guest_cid;
 	bool seqpacket_allow;
 	bool ever_started; /* set on first SET_RUNNING(1); never cleared */
+	bool dgram_allow;
 };
 
 static u32 vhost_transport_get_local_cid(void)
@@ -183,6 +185,14 @@ vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
 		 * buffer, we split it using multiple buffers.
 		 */
 		if (payload_len > iov_len - sizeof(*hdr)) {
+			/* Datagrams must fit in a single descriptor chain. */
+			if (le16_to_cpu(hdr->type) == VIRTIO_VSOCK_TYPE_DGRAM) {
+				kfree_skb(skb);
+				vhost_add_used(vq, head, 0);
+				added = true;
+				continue;
+			}
+
 			payload_len = iov_len - sizeof(*hdr);
 
 			/* As we are copying pieces of large packet's buffer to
@@ -436,6 +446,8 @@ static bool vhost_transport_msgzerocopy_allow(void)
 
 static bool vhost_transport_seqpacket_allow(struct vsock_sock *vsk,
 					    u32 remote_cid);
+static bool vhost_transport_dgram_allow(struct vsock_sock *vsk,
+					u32 cid, u32 port);
 
 static bool
 vhost_transport_stream_allow(struct vsock_sock *vsk, u32 cid, u32 port)
@@ -460,7 +472,8 @@ static struct virtio_transport vhost_transport = {
 		.dgram_enqueue            = virtio_transport_dgram_enqueue,
 		.dgram_dequeue            = virtio_transport_dgram_dequeue,
 		.dgram_bind               = virtio_transport_dgram_bind,
-		.dgram_allow              = virtio_transport_dgram_allow,
+		.dgram_allow              = vhost_transport_dgram_allow,
+		.dgram_transport_match    = virtio_transport_dgram_transport_match,
 
 		.stream_enqueue           = virtio_transport_stream_enqueue,
 		.stream_dequeue           = virtio_transport_stream_dequeue,
@@ -516,6 +529,24 @@ static bool vhost_transport_seqpacket_allow(struct vsock_sock *vsk,
 	return seqpacket_allow;
 }
 
+static bool vhost_transport_dgram_allow(struct vsock_sock *vsk,
+					u32 cid, u32 port)
+{
+	struct net *net = sock_net(sk_vsock(vsk));
+	struct vhost_vsock *vsock;
+	bool dgram_allow = false;
+
+	rcu_read_lock();
+	vsock = vhost_vsock_get(cid, net);
+
+	if (vsock)
+		dgram_allow = vsock->dgram_allow;
+
+	rcu_read_unlock();
+
+	return dgram_allow;
+}
+
 static void vhost_vsock_handle_tx_kick(struct vhost_work *work)
 {
 	struct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue,
@@ -737,6 +768,7 @@ static int vhost_vsock_dev_open(struct inode *inode, struct file *file)
 	vsock->guest_cid = 0; /* no CID assigned yet */
 	vsock->seqpacket_allow = false;
 	vsock->ever_started = false;
+	vsock->dgram_allow = false;
 
 	atomic_set(&vsock->queued_replies, 0);
 
@@ -890,6 +922,7 @@ static int vhost_vsock_set_features(struct vhost_vsock *vsock, u64 features)
 	}
 
 	vsock->seqpacket_allow = features & (1ULL << VIRTIO_VSOCK_F_SEQPACKET);
+	vsock->dgram_allow = features & (1ULL << VIRTIO_VSOCK_F_DGRAM);
 
 	for (i = 0; i < ARRAY_SIZE(vsock->vqs); i++) {
 		vq = &vsock->vqs[i];
diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h
index f91704731057ed..91945031fcc535 100644
--- a/include/linux/virtio_vsock.h
+++ b/include/linux/virtio_vsock.h
@@ -12,6 +12,9 @@
 struct virtio_vsock_skb_cb {
 	bool reply;
 	bool tap_delivered;
+	/* Datagram payload is still charged to bytes_unsent. */
+	bool tx_accounted;
+	bool to_host;
 	u32 offset;
 };
 
@@ -171,6 +174,7 @@ struct virtio_vsock_sock {
 
 struct virtio_vsock_pkt_info {
 	u32 remote_cid, remote_port;
+	const struct virtio_transport *t_ops;
 	struct vsock_sock *vsk;
 	struct msghdr *msg;
 	struct net *net;
@@ -261,6 +265,7 @@ bool virtio_transport_stream_allow(struct vsock_sock *vsk, u32 cid, u32 port);
 int virtio_transport_dgram_bind(struct vsock_sock *vsk,
 				struct sockaddr_vm *addr);
 bool virtio_transport_dgram_allow(struct vsock_sock *vsk, u32 cid, u32 port);
+bool virtio_transport_dgram_transport_match(const struct vsock_transport *t);
 
 int virtio_transport_connect(struct vsock_sock *vsk);
 
@@ -274,6 +279,7 @@ virtio_transport_stream_enqueue(struct vsock_sock *vsk,
 				size_t len);
 int
 virtio_transport_dgram_enqueue(struct vsock_sock *vsk,
+			       const struct vsock_transport *transport,
 			       struct sockaddr_vm *remote_addr,
 			       struct msghdr *msg,
 			       size_t len);
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index e8481f12312e91..f9bcd703a7768a 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -121,9 +121,15 @@ struct vsock_transport {
 	int (*dgram_bind)(struct vsock_sock *, struct sockaddr_vm *);
 	int (*dgram_dequeue)(struct vsock_sock *vsk, struct msghdr *msg,
 			     size_t len, int flags);
-	int (*dgram_enqueue)(struct vsock_sock *, struct sockaddr_vm *,
-			     struct msghdr *, size_t len);
+	int (*dgram_enqueue)(struct vsock_sock *vsk,
+			     const struct vsock_transport *transport,
+			     struct sockaddr_vm *remote_addr,
+			     struct msghdr *msg, size_t len);
 	bool (*dgram_allow)(struct vsock_sock *vsk, u32 cid, u32 port);
+	/* Optional: another transport can use this socket's datagram state. */
+	bool (*dgram_transport_match)(const struct vsock_transport *transport);
+	/* Optional payload length override, called with the receive queue locked. */
+	size_t (*dgram_payload_len)(const struct sk_buff *skb);
 
 	/* STREAM. */
 	/* TODO: stream_bind() */
@@ -220,6 +226,7 @@ void vsock_remove_pending(struct sock *listener, struct sock *pending);
 void vsock_enqueue_accept(struct sock *listener, struct sock *connected);
 void vsock_pending_to_accept(struct sock *listener, struct sock *pending);
 void vsock_insert_connected(struct vsock_sock *vsk);
+int vsock_bind_common(struct vsock_sock *vsk, struct sockaddr_vm *addr);
 void vsock_remove_bound(struct vsock_sock *vsk);
 void vsock_remove_connected(struct vsock_sock *vsk);
 struct sock *vsock_find_bound_socket(struct sockaddr_vm *addr);
@@ -237,6 +244,9 @@ void vsock_remove_sock(struct vsock_sock *vsk);
 void vsock_for_each_connected_socket(struct vsock_transport *transport,
 				     void (*fn)(struct sock *sk));
 int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk);
+const struct vsock_transport *
+vsock_dgram_get_transport(struct vsock_sock *vsk,
+			  struct sockaddr_vm *remote_addr);
 bool vsock_find_cid(unsigned int cid);
 void vsock_linger(struct sock *sk);
 
diff --git a/include/uapi/linux/virtio_vsock.h b/include/uapi/linux/virtio_vsock.h
index 64738838bee573..27b4b2b8bf13b9 100644
--- a/include/uapi/linux/virtio_vsock.h
+++ b/include/uapi/linux/virtio_vsock.h
@@ -40,6 +40,7 @@
 
 /* The feature bitmap for virtio vsock */
 #define VIRTIO_VSOCK_F_SEQPACKET	1	/* SOCK_SEQPACKET supported */
+#define VIRTIO_VSOCK_F_DGRAM		3	/* SOCK_DGRAM supported */
 
 struct virtio_vsock_config {
 	__le64 guest_cid;
@@ -69,6 +70,7 @@ struct virtio_vsock_hdr {
 enum virtio_vsock_type {
 	VIRTIO_VSOCK_TYPE_STREAM = 1,
 	VIRTIO_VSOCK_TYPE_SEQPACKET = 2,
+	VIRTIO_VSOCK_TYPE_DGRAM = 3,
 };
 
 enum virtio_vsock_op {
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index d0508887a2fbac..6419498a6fe276 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -136,6 +136,7 @@
 #include <linux/types.h>
 #include <linux/bitops.h>
 #include <linux/cred.h>
+#include <linux/err.h>
 #include <linux/errqueue.h>
 #include <linux/init.h>
 #include <linux/io.h>
@@ -599,6 +600,7 @@ static void vsock_deassign_transport(struct vsock_sock *vsk)
 int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
 {
 	const struct vsock_transport *new_transport;
+	const struct vsock_transport *fallback_transport = NULL;
 	struct sock *sk = sk_vsock(vsk);
 	unsigned int remote_cid = vsk->remote_addr.svm_cid;
 	__u8 remote_flags;
@@ -622,7 +624,12 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
 
 	switch (sk->sk_type) {
 	case SOCK_DGRAM:
-		new_transport = transport_dgram;
+		if (transport_dgram)
+			new_transport = transport_dgram;
+		else if (transport_g2h)
+			new_transport = transport_g2h;
+		else
+			new_transport = transport_h2g;
 		break;
 	case SOCK_STREAM:
 	case SOCK_SEQPACKET:
@@ -661,6 +668,11 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
 		ret = -ENODEV;
 		goto err;
 	}
+	if (sk->sk_type == SOCK_DGRAM && !transport_dgram &&
+	    new_transport == transport_g2h && transport_h2g &&
+	    transport_h2g != new_transport &&
+	    try_module_get(transport_h2g->module))
+		fallback_transport = transport_h2g;
 
 	/* It's safe to release the mutex after a successful try_module_get().
 	 * Whichever transport `new_transport` points at, it won't go away until
@@ -697,6 +709,14 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
 	}
 
 	ret = new_transport->init(vsk, psk);
+	if (ret == -ESOCKTNOSUPPORT && fallback_transport) {
+		module_put(new_transport->module);
+		new_transport = fallback_transport;
+		fallback_transport = NULL;
+		ret = new_transport->init(vsk, psk);
+	}
+	if (fallback_transport)
+		module_put(fallback_transport->module);
 	if (ret) {
 		module_put(new_transport->module);
 		return ret;
@@ -711,6 +731,98 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
 }
 EXPORT_SYMBOL_GPL(vsock_assign_transport);
 
+/**
+ * vsock_dgram_get_transport - select a transport for a datagram destination
+ * @vsk: socket whose datagram state will be used
+ * @remote_addr: destination address
+ *
+ * The caller must hold the socket lock and release the returned transport's
+ * module reference after use. Dedicated datagram transports retain their
+ * original selection; compatible transports share the socket's existing state.
+ * Parent fallback sets VMADDR_FLAG_TO_HOST to preserve that route on connect.
+ *
+ * Return: a referenced transport or an ERR_PTR on failure.
+ */
+const struct vsock_transport *
+vsock_dgram_get_transport(struct vsock_sock *vsk,
+			  struct sockaddr_vm *remote_addr)
+{
+	const struct vsock_transport *owner = vsk->transport;
+	const struct vsock_transport *transport;
+	struct sock *sk = sk_vsock(vsk);
+	u32 cid = remote_addr->svm_cid;
+	u32 local_cid;
+	bool to_host = false;
+	int err = -ENODEV;
+
+	if (!owner)
+		return ERR_PTR(-ENODEV);
+
+	mutex_lock(&vsock_register_mutex);
+	if (!owner->dgram_transport_match) {
+		transport = owner;
+	} else if (vsock_use_local_transport(cid)) {
+		transport = transport_local;
+	} else if (cid <= VMADDR_CID_HOST ||
+		   (remote_addr->svm_flags & VMADDR_FLAG_TO_HOST)) {
+		transport = transport_g2h;
+	} else if (transport_h2g &&
+		   (!transport_h2g->has_remote_cid ||
+		    transport_h2g->has_remote_cid(vsk, cid))) {
+		transport = transport_h2g;
+	} else if (sock_net(sk)->vsock.g2h_fallback && transport_g2h &&
+		   transport_g2h->has_remote_cid &&
+		   transport_g2h->has_remote_cid(vsk, cid)) {
+		transport = transport_g2h;
+		to_host = true;
+	} else {
+		transport = transport_h2g;
+	}
+
+	if (!transport)
+		goto out;
+	if (owner->dgram_transport_match &&
+	    !owner->dgram_transport_match(transport)) {
+		err = -ESOCKTNOSUPPORT;
+		goto out;
+	}
+	/* Loopback source addresses must route replies back to loopback. */
+	if (owner->dgram_transport_match && transport == transport_local &&
+	    vsk->local_addr.svm_cid != VMADDR_CID_ANY &&
+	    !vsock_use_local_transport(vsk->local_addr.svm_cid)) {
+		err = -EADDRNOTAVAIL;
+		goto out;
+	}
+	if (!try_module_get(transport->module))
+		goto out;
+	mutex_unlock(&vsock_register_mutex);
+
+	if (owner->dgram_transport_match) {
+		local_cid = transport->get_local_cid();
+		if (vsk->local_addr.svm_cid != VMADDR_CID_ANY &&
+		    local_cid != VMADDR_CID_LOCAL &&
+		    vsk->local_addr.svm_cid != local_cid) {
+			err = -EADDRNOTAVAIL;
+			goto put_transport;
+		}
+	}
+	if (!transport->dgram_allow(vsk, cid, remote_addr->svm_port)) {
+		err = -EINVAL;
+		goto put_transport;
+	}
+	if (to_host)
+		remote_addr->svm_flags |= VMADDR_FLAG_TO_HOST;
+	return transport;
+
+put_transport:
+	module_put(transport->module);
+	return ERR_PTR(err);
+out:
+	mutex_unlock(&vsock_register_mutex);
+	return ERR_PTR(err);
+}
+EXPORT_SYMBOL_GPL(vsock_dgram_get_transport);
+
 /*
  * Provide safe access to static transport_{h2g,g2h,dgram,local} callbacks.
  * Otherwise we may race with module removal. Do not use on `vsk->transport`.
@@ -832,8 +944,8 @@ static void vsock_pending_work(struct work_struct *work)
 
 /**** SOCKET OPERATIONS ****/
 
-static int __vsock_bind_connectible(struct vsock_sock *vsk,
-				    struct sockaddr_vm *addr)
+static int __vsock_bind_common(struct vsock_sock *vsk,
+			       struct sockaddr_vm *addr)
 {
 	struct net *net = sock_net(sk_vsock(vsk));
 	struct sockaddr_vm new_addr;
@@ -888,11 +1000,26 @@ static int __vsock_bind_connectible(struct vsock_sock *vsk,
 	return 0;
 }
 
-static int __vsock_bind_dgram(struct vsock_sock *vsk,
-			      struct sockaddr_vm *addr)
+/**
+ * vsock_bind_common - bind a socket using the core port allocator
+ * @vsk: socket to bind
+ * @addr: local address to bind to
+ *
+ * The caller must hold the socket lock.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int vsock_bind_common(struct vsock_sock *vsk, struct sockaddr_vm *addr)
 {
-	return vsk->transport->dgram_bind(vsk, addr);
+	int ret;
+
+	spin_lock_bh(&vsock_table_lock);
+	ret = __vsock_bind_common(vsk, addr);
+	spin_unlock_bh(&vsock_table_lock);
+
+	return ret;
 }
+EXPORT_SYMBOL_GPL(vsock_bind_common);
 
 static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr)
 {
@@ -914,13 +1041,11 @@ static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr)
 	switch (sk->sk_socket->type) {
 	case SOCK_STREAM:
 	case SOCK_SEQPACKET:
-		spin_lock_bh(&vsock_table_lock);
-		retval = __vsock_bind_connectible(vsk, addr);
-		spin_unlock_bh(&vsock_table_lock);
+		retval = vsock_bind_common(vsk, addr);
 		break;
 
 	case SOCK_DGRAM:
-		retval = __vsock_bind_dgram(vsk, addr);
+		retval = vsk->transport->dgram_bind(vsk, addr);
 		break;
 
 	default:
@@ -1027,7 +1152,7 @@ static void __vsock_release(struct sock *sk, int level)
 
 	if (vsk->transport)
 		vsk->transport->release(vsk);
-	else if (sock_type_connectible(sk->sk_type))
+	else
 		vsock_remove_sock(vsk);
 
 	sock_orphan(sk);
@@ -1331,16 +1456,20 @@ static __poll_t vsock_poll(struct file *file, struct socket *sock,
 		mask |= vsock_poll_shutdown(sk, peer_shutdown);
 
 		/* For datagram sockets we can read if there is something in
-		 * the queue and write as long as the socket isn't shutdown for
-		 * sending.
+		 * the queue and write if the send buffer has space and the
+		 * socket isn't shutdown for sending.
 		 */
 		if (!skb_queue_empty_lockless(&sk->sk_receive_queue) ||
 		    (sk->sk_shutdown & RCV_SHUTDOWN)) {
 			mask |= EPOLLIN | EPOLLRDNORM;
 		}
 
-		if (!(sk->sk_shutdown & SEND_SHUTDOWN))
-			mask |= EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND;
+		if (!(sk->sk_shutdown & SEND_SHUTDOWN)) {
+			if (sock_writeable(sk))
+				mask |= EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND;
+			else
+				sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);
+		}
 
 	} else if (sock_type_connectible(sk->sk_type)) {
 		const struct vsock_transport *transport;
@@ -1466,7 +1595,8 @@ static int vsock_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
 		 */
 
 		if (remote_addr->svm_cid == VMADDR_CID_ANY)
-			remote_addr->svm_cid = transport->get_local_cid();
+			remote_addr->svm_cid = transport->dgram_transport_match ?
+				VMADDR_CID_LOCAL : transport->get_local_cid();
 
 		if (!vsock_addr_bound(remote_addr)) {
 			err = -EINVAL;
@@ -1476,7 +1606,8 @@ static int vsock_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
 		remote_addr = &vsk->remote_addr;
 
 		if (remote_addr->svm_cid == VMADDR_CID_ANY)
-			remote_addr->svm_cid = transport->get_local_cid();
+			remote_addr->svm_cid = transport->dgram_transport_match ?
+				VMADDR_CID_LOCAL : transport->get_local_cid();
 
 		/* XXX Should connect() or this function ensure remote_addr is
 		 * bound?
@@ -1490,13 +1621,14 @@ static int vsock_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
 		goto out;
 	}
 
-	if (!transport->dgram_allow(vsk, remote_addr->svm_cid,
-				    remote_addr->svm_port)) {
-		err = -EINVAL;
+	transport = vsock_dgram_get_transport(vsk, remote_addr);
+	if (IS_ERR(transport)) {
+		err = PTR_ERR(transport);
 		goto out;
 	}
 
-	err = transport->dgram_enqueue(vsk, remote_addr, msg, len);
+	err = transport->dgram_enqueue(vsk, transport, remote_addr, msg, len);
+	module_put(transport->module);
 
 out:
 	release_sock(sk);
@@ -1510,6 +1642,7 @@ static int vsock_dgram_connect(struct socket *sock,
 	struct sock *sk;
 	struct vsock_sock *vsk;
 	struct sockaddr_vm *remote_addr;
+	const struct vsock_transport *transport;
 
 	sk = sock->sk;
 	vsk = vsock_sk(sk);
@@ -1531,11 +1664,16 @@ static int vsock_dgram_connect(struct socket *sock,
 	if (err)
 		goto out;
 
-	if (!vsk->transport->dgram_allow(vsk, remote_addr->svm_cid,
-					 remote_addr->svm_port)) {
-		err = -EINVAL;
+	if (remote_addr->svm_cid == VMADDR_CID_ANY &&
+	    vsk->transport->dgram_transport_match)
+		remote_addr->svm_cid = VMADDR_CID_LOCAL;
+
+	transport = vsock_dgram_get_transport(vsk, remote_addr);
+	if (IS_ERR(transport)) {
+		err = PTR_ERR(transport);
 		goto out;
 	}
+	module_put(transport->module);
 
 	memcpy(&vsk->remote_addr, remote_addr, sizeof(vsk->remote_addr));
 	sock->state = SS_CONNECTED;
@@ -1604,10 +1742,22 @@ static int vsock_do_ioctl(struct socket *sock, unsigned int cmd,
 			break;
 		}
 
-		n_bytes = vsock_stream_has_data(vsk);
-		if (n_bytes < 0) {
-			ret = n_bytes;
-			break;
+		if (sk->sk_type == SOCK_DGRAM) {
+			struct sk_buff *skb;
+
+			spin_lock_bh(&sk->sk_receive_queue.lock);
+			skb = skb_peek(&sk->sk_receive_queue);
+			if (skb && vsk->transport->dgram_payload_len)
+				n_bytes = vsk->transport->dgram_payload_len(skb);
+			else
+				n_bytes = skb ? skb->len : 0;
+			spin_unlock_bh(&sk->sk_receive_queue.lock);
+		} else {
+			n_bytes = vsock_stream_has_data(vsk);
+			if (n_bytes < 0) {
+				ret = n_bytes;
+				break;
+			}
 		}
 		ret = put_user(n_bytes, arg);
 		break;
diff --git a/net/vmw_vsock/hyperv_transport.c b/net/vmw_vsock/hyperv_transport.c
index 855b15971c359f..6a276ef6964ba1 100644
--- a/net/vmw_vsock/hyperv_transport.c
+++ b/net/vmw_vsock/hyperv_transport.c
@@ -443,6 +443,9 @@ static int hvs_sock_init(struct vsock_sock *vsk, struct vsock_sock *psk)
 	struct hvsock *hvs;
 	struct sock *sk = sk_vsock(vsk);
 
+	if (sk->sk_type == SOCK_DGRAM)
+		return -ESOCKTNOSUPPORT;
+
 	hvs = kzalloc_obj(*hvs);
 	if (!hvs)
 		return -ENOMEM;
@@ -563,6 +566,7 @@ static int hvs_dgram_dequeue(struct vsock_sock *vsk, struct msghdr *msg,
 }
 
 static int hvs_dgram_enqueue(struct vsock_sock *vsk,
+			     const struct vsock_transport *transport,
 			     struct sockaddr_vm *remote, struct msghdr *msg,
 			     size_t dgram_len)
 {
diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
index 4f9aa9c4c3aa59..d256274088b38c 100644
--- a/net/vmw_vsock/virtio_transport.c
+++ b/net/vmw_vsock/virtio_transport.c
@@ -54,9 +54,11 @@ struct virtio_vsock {
 	bool rx_run;
 	int rx_buf_nr;
 	int rx_buf_max_nr;
+	struct scatterlist in_bufs[MAX_SKB_FRAGS + 1];
 
 	u32 guest_cid;
 	bool seqpacket_allow;
+	bool dgram_allow;
 
 	/* These fields are used only in tx path in function
 	 * 'virtio_transport_send_pkt_work()', so to save
@@ -311,22 +313,31 @@ virtio_transport_cancel_pkt(struct vsock_sock *vsk)
 static void virtio_vsock_rx_fill(struct virtio_vsock *vsock)
 {
 	int total_len = VIRTIO_VSOCK_DEFAULT_RX_BUF_SIZE;
-	struct scatterlist pkt, *p;
 	struct virtqueue *vq;
 	struct sk_buff *skb;
 	int ret;
 
 	vq = vsock->vqs[VSOCK_VQ_RX];
+	/* A datagram must fit in one RX buffer, including its header. */
+	if (vsock->dgram_allow)
+		total_len = VIRTIO_VSOCK_SKB_HEADROOM +
+			    VIRTIO_VSOCK_MAX_PKT_BUF_SIZE;
 
 	do {
-		skb = virtio_vsock_alloc_linear_skb(total_len, GFP_KERNEL);
+		skb = virtio_vsock_alloc_skb(total_len, GFP_KERNEL);
 		if (!skb)
 			break;
 
 		memset(skb->head, 0, VIRTIO_VSOCK_SKB_HEADROOM);
-		sg_init_one(&pkt, virtio_vsock_hdr(skb), total_len);
-		p = &pkt;
-		ret = virtqueue_add_sgs(vq, &p, 0, 1, skb, GFP_KERNEL);
+		/* Post the header and the entire payload as one RX buffer. */
+		virtio_vsock_skb_put(skb, total_len - VIRTIO_VSOCK_SKB_HEADROOM);
+		skb_push(skb, VIRTIO_VSOCK_SKB_HEADROOM);
+		sg_init_table(vsock->in_bufs, ARRAY_SIZE(vsock->in_bufs));
+		ret = skb_to_sgvec(skb, vsock->in_bufs, 0, total_len);
+		skb_pull(skb, VIRTIO_VSOCK_SKB_HEADROOM);
+		if (ret >= 0)
+			ret = virtqueue_add_inbuf(vq, vsock->in_bufs, ret, skb,
+						  GFP_KERNEL);
 		if (ret < 0) {
 			kfree_skb(skb);
 			break;
@@ -547,6 +558,18 @@ bool virtio_transport_stream_allow(struct vsock_sock *vsk, u32 cid, u32 port)
 
 static bool virtio_transport_seqpacket_allow(struct vsock_sock *vsk,
 					     u32 remote_cid);
+static bool virtio_transport_dgram_allow_check(struct vsock_sock *vsk,
+					       u32 cid, u32 port);
+
+static int virtio_transport_socket_init(struct vsock_sock *vsk,
+					struct vsock_sock *psk)
+{
+	if (sk_vsock(vsk)->sk_type == SOCK_DGRAM &&
+	    !virtio_transport_dgram_allow_check(vsk, 0, 0))
+		return -ESOCKTNOSUPPORT;
+
+	return virtio_transport_do_socket_init(vsk, psk);
+}
 
 static bool virtio_transport_has_remote_cid(struct vsock_sock *vsk, u32 cid)
 {
@@ -561,7 +584,7 @@ static struct virtio_transport virtio_transport = {
 		.get_local_cid            = virtio_transport_get_local_cid,
 		.has_remote_cid           = virtio_transport_has_remote_cid,
 
-		.init                     = virtio_transport_do_socket_init,
+		.init                     = virtio_transport_socket_init,
 		.destruct                 = virtio_transport_destruct,
 		.release                  = virtio_transport_release,
 		.connect                  = virtio_transport_connect,
@@ -571,7 +594,8 @@ static struct virtio_transport virtio_transport = {
 		.dgram_bind               = virtio_transport_dgram_bind,
 		.dgram_dequeue            = virtio_transport_dgram_dequeue,
 		.dgram_enqueue            = virtio_transport_dgram_enqueue,
-		.dgram_allow              = virtio_transport_dgram_allow,
+		.dgram_allow              = virtio_transport_dgram_allow_check,
+		.dgram_transport_match    = virtio_transport_dgram_transport_match,
 
 		.stream_dequeue           = virtio_transport_stream_dequeue,
 		.stream_enqueue           = virtio_transport_stream_enqueue,
@@ -629,6 +653,25 @@ virtio_transport_seqpacket_allow(struct vsock_sock *vsk, u32 remote_cid)
 	return seqpacket_allow;
 }
 
+static bool
+virtio_transport_dgram_allow_check(struct vsock_sock *vsk, u32 cid, u32 port)
+{
+	struct virtio_vsock *vsock;
+	bool dgram_allow;
+
+	if (!vsock_net_mode_global(vsk))
+		return false;
+
+	dgram_allow = false;
+	rcu_read_lock();
+	vsock = rcu_dereference(the_virtio_vsock);
+	if (vsock)
+		dgram_allow = vsock->dgram_allow;
+	rcu_read_unlock();
+
+	return dgram_allow;
+}
+
 static void virtio_transport_rx_work(struct work_struct *work)
 {
 	struct virtio_vsock *vsock =
@@ -665,7 +708,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))) {
+				     len > sizeof(*hdr) + skb->len)) {
 				kfree_skb(skb);
 				continue;
 			}
@@ -677,8 +720,7 @@ static void virtio_transport_rx_work(struct work_struct *work)
 				continue;
 			}
 
-			if (payload_len)
-				virtio_vsock_skb_put(skb, payload_len);
+			pskb_trim_unique(skb, payload_len);
 
 			virtio_transport_deliver_tap_pkt(skb);
 
@@ -690,7 +732,8 @@ static void virtio_transport_rx_work(struct work_struct *work)
 	} while (!virtqueue_enable_cb(vq));
 
 out:
-	if (vsock->rx_buf_nr < vsock->rx_buf_max_nr / 2)
+	if (!vsock->rx_buf_nr ||
+	    vsock->rx_buf_nr < vsock->rx_buf_max_nr / 2)
 		virtio_vsock_rx_fill(vsock);
 out_nofill:
 	mutex_unlock(&vsock->rx_lock);
@@ -835,6 +878,9 @@ static int virtio_vsock_probe(struct virtio_device *vdev)
 	if (virtio_has_feature(vdev, VIRTIO_VSOCK_F_SEQPACKET))
 		vsock->seqpacket_allow = true;
 
+	if (virtio_has_feature(vdev, VIRTIO_VSOCK_F_DGRAM))
+		vsock->dgram_allow = true;
+
 	vdev->priv = vsock;
 
 	ret = virtio_vsock_vqs_init(vsock);
@@ -932,7 +978,8 @@ static struct virtio_device_id id_table[] = {
 };
 
 static unsigned int features[] = {
-	VIRTIO_VSOCK_F_SEQPACKET
+	VIRTIO_VSOCK_F_SEQPACKET,
+	VIRTIO_VSOCK_F_DGRAM,
 };
 
 static struct virtio_driver virtio_vsock_driver = {
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index 1e762a480df490..7c1ec8feb853c9 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -204,10 +204,18 @@ static u16 virtio_transport_get_type(struct sock *sk)
 {
 	if (sk->sk_type == SOCK_STREAM)
 		return VIRTIO_VSOCK_TYPE_STREAM;
+	else if (sk->sk_type == SOCK_DGRAM)
+		return VIRTIO_VSOCK_TYPE_DGRAM;
 	else
 		return VIRTIO_VSOCK_TYPE_SEQPACKET;
 }
 
+static void virtio_transport_dgram_skb_destructor(struct sk_buff *skb)
+{
+	virtio_transport_consume_skb_sent(skb, false);
+	sock_wfree(skb);
+}
+
 /* Returns new sk_buff on success, otherwise returns NULL. */
 static struct sk_buff *virtio_transport_alloc_skb(struct virtio_vsock_pkt_info *info,
 						  size_t payload_len,
@@ -303,6 +311,7 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
 	u32 src_cid, src_port, dst_cid, dst_port;
 	const struct virtio_transport *t_ops;
 	struct iov_iter_state msg_iter_state;
+	struct sock *sk = sk_vsock(vsk);
 	struct virtio_vsock_sock *vvs;
 	struct ubuf_info *uarg = NULL;
 	u32 pkt_len = info->pkt_len;
@@ -313,13 +322,18 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
 
 	info->type = virtio_transport_get_type(sk_vsock(vsk));
 
-	t_ops = virtio_transport_get_ops(vsk);
+	t_ops = info->t_ops ?: virtio_transport_get_ops(vsk);
 	if (unlikely(!t_ops))
 		return -EFAULT;
 
 	src_cid = t_ops->transport.get_local_cid();
+	/* Preserve explicit datagram bindings so loopback replies can find them. */
+	if (info->type == VIRTIO_VSOCK_TYPE_DGRAM &&
+	    src_cid == VMADDR_CID_LOCAL &&
+	    vsk->local_addr.svm_cid != VMADDR_CID_ANY)
+		src_cid = vsk->local_addr.svm_cid;
 	src_port = vsk->local_addr.svm_port;
-	if (!info->remote_cid) {
+	if (info->type != VIRTIO_VSOCK_TYPE_DGRAM && !info->remote_cid) {
 		dst_cid	= vsk->remote_addr.svm_cid;
 		dst_port = vsk->remote_addr.svm_port;
 	} else {
@@ -329,12 +343,24 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
 
 	vvs = vsk->trans;
 
-	/* virtio_transport_get_credit might return less than pkt_len credit */
-	pkt_len = virtio_transport_get_credit(vvs, pkt_len);
+	if (info->type == VIRTIO_VSOCK_TYPE_DGRAM) {
+		if (pkt_len > VIRTIO_VSOCK_MAX_PKT_BUF_SIZE)
+			return -EMSGSIZE;
+
+		/* Datagram sends are serialized by the socket lock. */
+		if (sk_wmem_alloc_get(sk) >= READ_ONCE(sk->sk_sndbuf)) {
+			sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);
+			set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
+			return -EAGAIN;
+		}
+	} else {
+		/* virtio_transport_get_credit might return less than pkt_len credit */
+		pkt_len = virtio_transport_get_credit(vvs, pkt_len);
 
-	/* Do not send zero length OP_RW pkt */
-	if (pkt_len == 0 && info->op == VIRTIO_VSOCK_OP_RW)
-		return pkt_len;
+		/* Do not send zero length OP_RW pkt */
+		if (pkt_len == 0 && info->op == VIRTIO_VSOCK_OP_RW)
+			return pkt_len;
+	}
 
 	if (info->msg && (info->msg->msg_flags & MSG_ZEROCOPY)) {
 		/* If 'info->msg' is not NULL, this is only VIRTIO_VSOCK_OP_RW.
@@ -348,7 +374,8 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
 			uarg = msg_zerocopy_realloc(sk_vsock(vsk), pkt_len,
 						    NULL, false);
 			if (!uarg) {
-				virtio_transport_put_credit(vvs, pkt_len);
+				if (info->type != VIRTIO_VSOCK_TYPE_DGRAM)
+					virtio_transport_put_credit(vvs, pkt_len);
 				return -ENOMEM;
 			}
 
@@ -393,7 +420,15 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
 			break;
 		}
 
-		virtio_transport_inc_tx_pkt(vvs, skb);
+		if (info->type == VIRTIO_VSOCK_TYPE_DGRAM) {
+			spin_lock_bh(&vvs->tx_lock);
+			vvs->bytes_unsent += skb_len;
+			spin_unlock_bh(&vvs->tx_lock);
+			VIRTIO_VSOCK_SKB_CB(skb)->tx_accounted = true;
+			skb->destructor = virtio_transport_dgram_skb_destructor;
+		} else {
+			virtio_transport_inc_tx_pkt(vvs, skb);
+		}
 
 		ret = t_ops->send_pkt(skb, info->net);
 		if (ret < 0)
@@ -416,7 +451,8 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
 	if (info->msg && ret < 0)
 		iov_iter_restore(&info->msg->msg_iter, &msg_iter_state);
 
-	virtio_transport_put_credit(vvs, rest_len);
+	if (info->type != VIRTIO_VSOCK_TYPE_DGRAM)
+		virtio_transport_put_credit(vvs, rest_len);
 
 	/* msg_zerocopy_realloc() initializes the ubuf_info refcnt to 1.
 	 * skb_zcopy_set() increases it for each skb, so we can drop that
@@ -620,7 +656,9 @@ void virtio_transport_consume_skb_sent(struct sk_buff *skb, bool consume)
 {
 	struct sock *s = skb->sk;
 
-	if (s && skb->len) {
+	if (s && skb->len &&
+	    (s->sk_type != SOCK_DGRAM ||
+	     VIRTIO_VSOCK_SKB_CB(skb)->tx_accounted)) {
 		struct vsock_sock *vs = vsock_sk(s);
 		struct virtio_vsock_sock *vvs;
 
@@ -631,6 +669,9 @@ void virtio_transport_consume_skb_sent(struct sk_buff *skb, bool consume)
 		spin_unlock_bh(&vvs->tx_lock);
 	}
 
+	if (s && s->sk_type == SOCK_DGRAM)
+		VIRTIO_VSOCK_SKB_CB(skb)->tx_accounted = false;
+
 	if (consume)
 		consume_skb(skb);
 }
@@ -999,7 +1040,49 @@ virtio_transport_dgram_dequeue(struct vsock_sock *vsk,
 			       struct msghdr *msg,
 			       size_t len, int flags)
 {
-	return -EOPNOTSUPP;
+	struct sock *sk = sk_vsock(vsk);
+	struct virtio_vsock_hdr *hdr;
+	struct sk_buff *skb;
+	size_t payload_len;
+	int err;
+
+	if (flags & (MSG_OOB | MSG_ERRQUEUE))
+		return -EOPNOTSUPP;
+
+	skb = skb_recv_datagram(sk, flags, &err);
+	if (!skb)
+		return err;
+
+	hdr = virtio_vsock_hdr(skb);
+	payload_len = skb->len;
+
+	if (payload_len > len) {
+		payload_len = len;
+		msg->msg_flags |= MSG_TRUNC;
+	}
+
+	err = skb_copy_datagram_iter(skb, 0, &msg->msg_iter, payload_len);
+	if (err)
+		goto out;
+
+	if (msg->msg_name) {
+		DECLARE_SOCKADDR(struct sockaddr_vm *, vm_addr, msg->msg_name);
+
+		vsock_addr_init(vm_addr, le64_to_cpu(hdr->src_cid),
+				le32_to_cpu(hdr->src_port));
+		msg->msg_namelen = sizeof(*vm_addr);
+		if (VIRTIO_VSOCK_SKB_CB(skb)->to_host)
+			vm_addr->svm_flags = VMADDR_FLAG_TO_HOST;
+	}
+
+	if (flags & MSG_TRUNC)
+		err = skb->len;
+	else
+		err = payload_len;
+
+out:
+	skb_free_datagram(sk, skb);
+	return err;
 }
 EXPORT_SYMBOL_GPL(virtio_transport_dgram_dequeue);
 
@@ -1202,16 +1285,23 @@ EXPORT_SYMBOL_GPL(virtio_transport_stream_is_active);
 int virtio_transport_dgram_bind(struct vsock_sock *vsk,
 				struct sockaddr_vm *addr)
 {
-	return -EOPNOTSUPP;
+	return vsock_bind_common(vsk, addr);
 }
 EXPORT_SYMBOL_GPL(virtio_transport_dgram_bind);
 
 bool virtio_transport_dgram_allow(struct vsock_sock *vsk, u32 cid, u32 port)
 {
-	return false;
+	return true;
 }
 EXPORT_SYMBOL_GPL(virtio_transport_dgram_allow);
 
+bool virtio_transport_dgram_transport_match(const struct vsock_transport *t)
+{
+	return t && t->dgram_transport_match ==
+		virtio_transport_dgram_transport_match;
+}
+EXPORT_SYMBOL_GPL(virtio_transport_dgram_transport_match);
+
 int virtio_transport_connect(struct vsock_sock *vsk)
 {
 	struct virtio_vsock_pkt_info info = {
@@ -1242,11 +1332,23 @@ EXPORT_SYMBOL_GPL(virtio_transport_shutdown);
 
 int
 virtio_transport_dgram_enqueue(struct vsock_sock *vsk,
+			       const struct vsock_transport *transport,
 			       struct sockaddr_vm *remote_addr,
 			       struct msghdr *msg,
 			       size_t dgram_len)
 {
-	return -EOPNOTSUPP;
+	struct virtio_vsock_pkt_info info = {
+		.op = VIRTIO_VSOCK_OP_RW,
+		.t_ops = container_of(transport, struct virtio_transport, transport),
+		.msg = msg,
+		.pkt_len = dgram_len,
+		.vsk = vsk,
+		.remote_cid = remote_addr->svm_cid,
+		.remote_port = remote_addr->svm_port,
+		.net = sock_net(sk_vsock(vsk)),
+	};
+
+	return virtio_transport_send_pkt_info(vsk, &info);
 }
 EXPORT_SYMBOL_GPL(virtio_transport_dgram_enqueue);
 
@@ -1771,7 +1873,35 @@ virtio_transport_recv_listen(struct sock *sk, struct sk_buff *skb,
 static bool virtio_transport_valid_type(u16 type)
 {
 	return (type == VIRTIO_VSOCK_TYPE_STREAM) ||
-	       (type == VIRTIO_VSOCK_TYPE_SEQPACKET);
+	       (type == VIRTIO_VSOCK_TYPE_SEQPACKET) ||
+	       (type == VIRTIO_VSOCK_TYPE_DGRAM);
+}
+
+static bool
+virtio_transport_dgram_source_allow(struct vsock_sock *vsk,
+				    const struct vsock_transport *transport,
+				    const struct sockaddr_vm *src)
+{
+	const struct vsock_transport *peer_transport;
+	struct sockaddr_vm peer = vsk->remote_addr;
+	bool allow;
+
+	if (!virtio_transport_dgram_transport_match(vsk->transport) ||
+	    !virtio_transport_dgram_transport_match(transport))
+		return false;
+	/* Shutdown preserves the peer; only disconnect removes it. */
+	if (sk_vsock(vsk)->sk_socket->state == SS_UNCONNECTED)
+		return true;
+
+	peer_transport = vsock_dgram_get_transport(vsk, &peer);
+	if (IS_ERR(peer_transport))
+		return false;
+	allow = peer_transport == transport &&
+		src->svm_port == vsk->remote_addr.svm_port &&
+		(src->svm_cid == vsk->remote_addr.svm_cid ||
+		 transport->get_local_cid() == VMADDR_CID_LOCAL);
+	module_put(peer_transport->module);
+	return allow;
 }
 
 /* We are under the virtio-vsock's vsock->rx_lock or vhost-vsock's vq->mutex
@@ -1805,6 +1935,41 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,
 		goto free_pkt;
 	}
 
+	/* Dgram packets are delivered directly to bound sockets without
+	 * going through the connection state machine. Undeliverable
+	 * datagrams are silently dropped (no RST).
+	 */
+	if (le16_to_cpu(hdr->type) == VIRTIO_VSOCK_TYPE_DGRAM) {
+		if (le16_to_cpu(hdr->op) != VIRTIO_VSOCK_OP_RW)
+			goto free_pkt;
+
+		sk = vsock_find_bound_socket_net(&dst, net);
+
+		if (!sk || sk->sk_type != SOCK_DGRAM) {
+			if (sk)
+				sock_put(sk);
+			goto free_pkt;
+		}
+
+		vsk = vsock_sk(sk);
+		lock_sock(sk);
+		if (sock_flag(sk, SOCK_DEAD) ||
+		    !virtio_transport_dgram_source_allow(vsk, &t->transport, &src)) {
+			release_sock(sk);
+			sock_put(sk);
+			goto free_pkt;
+		}
+
+		VIRTIO_VSOCK_SKB_CB(skb)->to_host =
+			t->transport.get_local_cid() > VMADDR_CID_HOST;
+		if (sock_queue_rcv_skb(sk, skb))
+			kfree_skb(skb);
+
+		release_sock(sk);
+		sock_put(sk);
+		return;
+	}
+
 	/* The socket must be in connected or bound table
 	 * otherwise send reset back
 	 */
diff --git a/net/vmw_vsock/vmci_transport.c b/net/vmw_vsock/vmci_transport.c
index 1f186e8f83640a..410deb2493c633 100644
--- a/net/vmw_vsock/vmci_transport.c
+++ b/net/vmw_vsock/vmci_transport.c
@@ -1723,6 +1723,7 @@ static int vmci_transport_dgram_bind(struct vsock_sock *vsk,
 
 static int vmci_transport_dgram_enqueue(
 	struct vsock_sock *vsk,
+	const struct vsock_transport *transport,
 	struct sockaddr_vm *remote_addr,
 	struct msghdr *msg,
 	size_t len)
@@ -1761,6 +1762,14 @@ static int vmci_transport_dgram_enqueue(
 	return err - sizeof(*dg);
 }
 
+static size_t vmci_transport_dgram_payload_len(const struct sk_buff *skb)
+{
+	if (skb->len < sizeof(struct vmci_datagram))
+		return 0;
+
+	return skb->len - sizeof(struct vmci_datagram);
+}
+
 static int vmci_transport_dgram_dequeue(struct vsock_sock *vsk,
 					struct msghdr *msg, size_t len,
 					int flags)
@@ -2075,6 +2084,7 @@ static struct vsock_transport vmci_transport = {
 	.connect = vmci_transport_connect,
 	.dgram_bind = vmci_transport_dgram_bind,
 	.dgram_dequeue = vmci_transport_dgram_dequeue,
+	.dgram_payload_len = vmci_transport_dgram_payload_len,
 	.dgram_enqueue = vmci_transport_dgram_enqueue,
 	.dgram_allow = vmci_transport_dgram_allow,
 	.stream_dequeue = vmci_transport_stream_dequeue,
diff --git a/net/vmw_vsock/vsock_bpf.c b/net/vmw_vsock/vsock_bpf.c
index 9049d26486460c..296e1fa7970b77 100644
--- a/net/vmw_vsock/vsock_bpf.c
+++ b/net/vmw_vsock/vsock_bpf.c
@@ -161,7 +161,8 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore
 	if (!vsk->transport)
 		return -ENODEV;
 
-	if (!vsk->transport->read_skb)
+	/* Datagram receive is not implemented by the sockmap callbacks. */
+	if (sk->sk_type == SOCK_DGRAM || !vsk->transport->read_skb)
 		return -EOPNOTSUPP;
 
 	vsock_bpf_check_needs_rebuild(psock->sk_proto);
diff --git a/net/vmw_vsock/vsock_loopback.c b/net/vmw_vsock/vsock_loopback.c
index 8068d1b6e85181..d7bff1da2b7486 100644
--- a/net/vmw_vsock/vsock_loopback.c
+++ b/net/vmw_vsock/vsock_loopback.c
@@ -77,6 +77,7 @@ static struct virtio_transport loopback_transport = {
 		.dgram_dequeue            = virtio_transport_dgram_dequeue,
 		.dgram_enqueue            = virtio_transport_dgram_enqueue,
 		.dgram_allow              = virtio_transport_dgram_allow,
+		.dgram_transport_match    = virtio_transport_dgram_transport_match,
 
 		.stream_dequeue           = virtio_transport_stream_dequeue,
 		.stream_enqueue           = virtio_transport_stream_enqueue,
diff --git a/tools/testing/vsock/Makefile b/tools/testing/vsock/Makefile
index 88211fd132d23e..78e2cb7b1193dd 100644
--- a/tools/testing/vsock/Makefile
+++ b/tools/testing/vsock/Makefile
@@ -1,7 +1,8 @@
 # SPDX-License-Identifier: GPL-2.0-only
 all: test vsock_perf
 test: vsock_test vsock_diag_test vsock_uring_test
-vsock_test: vsock_test.o vsock_test_zerocopy.o timeout.o control.o util.o msg_zerocopy_common.o
+vsock_test: vsock_test.o vsock_test_zerocopy.o vsock_test_dgram.o \
+	    timeout.o control.o util.o msg_zerocopy_common.o
 vsock_diag_test: vsock_diag_test.o timeout.o control.o util.o
 vsock_perf: vsock_perf.o msg_zerocopy_common.o
 
diff --git a/tools/testing/vsock/README b/tools/testing/vsock/README
index 680ce666ceb56d..be34b032ef1351 100644
--- a/tools/testing/vsock/README
+++ b/tools/testing/vsock/README
@@ -5,7 +5,7 @@ Hyper-V.
 
 The following tests are available:
 
-  * vsock_test - core AF_VSOCK socket functionality
+  * vsock_test - stream, seqpacket and datagram functionality
   * vsock_diag_test - vsock_diag.ko module for listing open sockets
 
 The following prerequisite steps are not automated and must be performed prior
@@ -32,7 +32,7 @@ Invoke test binaries in both directions as follows:
                         --control-port=1234 \
                         --peer-cid=2
   (host)# $TEST_BINARY --mode=client \
-                       --control-port=$GUEST_IP \
+                       --control-host=$GUEST_IP \
                        --control-port=1234 \
                        --peer-cid=3
 
@@ -51,6 +51,25 @@ suite run, e.g.
 
 For more information see Documentation/dev-tools/kmemleak.rst.
 
+Datagram tests
+--------------
+vsock_test includes datagram cases in the client/server runs above. Unsupported
+cases print a skip reason.
+
+Virtio/vhost datagrams require kernel support on both sides and a VMM that
+negotiates VIRTIO_VSOCK_F_DGRAM. Load vsock_loopback on both host and guest for
+the local cases, and make /dev/vsock available. VMCI's dedicated datagram
+transport takes priority when loaded, causing virtio/vhost cases to skip.
+
+Run only the local datagram cases without a peer or TCP control connection:
+
+  ./vsock_test --local
+
+Use --list and --pick <test_id> to select individual cases. The BPF admission
+test (48, requiring BPF privileges) and nested routing test (49, requiring both
+virtio and vhost transports) need explicit selection. The VMCI queue-length
+test (53) runs only with VMCI.
+
 vsock_perf utility
 -------------------
 'vsock_perf' is a simple tool to measure vsock performance. It works in
diff --git a/tools/testing/vsock/util.c b/tools/testing/vsock/util.c
index 299a4e8ecb6345..6ad97403697f18 100644
--- a/tools/testing/vsock/util.c
+++ b/tools/testing/vsock/util.c
@@ -499,47 +499,55 @@ void run_tests(const struct test_case *test_cases,
 	int i;
 
 	for (i = 0; test_cases[i].name; i++) {
+		const struct test_case *test = &test_cases[i];
 		void (*run)(const struct test_opts *opts);
+		const char *reason;
 		char *line;
 
-		printf("%d - %s...", i, test_cases[i].name);
+		if (opts->local && !test->local)
+			continue;
+		printf("%d - %s...", i, test->name);
 		fflush(stdout);
 
-		/* Full barrier before executing the next test.  This
-		 * ensures that client and server are executing the
-		 * same test case.  In particular, it means whoever is
-		 * faster will not see the peer still executing the
-		 * last test.  This is important because port numbers
-		 * can be used by multiple test cases.
-		 */
-		if (test_cases[i].skip)
-			control_writeln("SKIP");
-		else
-			control_writeln("NEXT");
-
-		line = control_readln();
-		if (control_cmpln(line, "SKIP", false) || test_cases[i].skip) {
-
-			printf("skipped\n");
-
+		if (opts->local) {
+			if (test->skip) {
+				printf("skipped\n");
+				continue;
+			}
+		} else {
+			/* Agree on explicit selection before any capability check
+			 * or test can exchange messages on the control connection.
+			 */
+			control_writeln(test->skip ? "SKIP" : "NEXT");
+			line = control_readln();
+			if (control_cmpln(line, "SKIP", false) || test->skip) {
+				printf("skipped\n");
+				free(line);
+				continue;
+			}
+			control_cmpln(line, "NEXT", true);
 			free(line);
-			continue;
 		}
 
-		control_cmpln(line, "NEXT", true);
-		free(line);
-
-		if (opts->mode == TEST_MODE_CLIENT)
-			run = test_cases[i].run_client;
+		if (test->check) {
+			reason = test->check(opts);
+			if (reason) {
+				printf("skipped: %s\n", reason);
+				continue;
+			}
+		}
+		if (opts->local || opts->mode == TEST_MODE_CLIENT)
+			run = test->run_client;
 		else
-			run = test_cases[i].run_server;
-
+			run = test->run_server;
 		if (run)
 			run(opts);
-
 		printf("ok\n");
 	}
 
+	if (opts->local)
+		return;
+
 	printf("All tests have been executed. Waiting other peer...");
 	fflush(stdout);
 
diff --git a/tools/testing/vsock/util.h b/tools/testing/vsock/util.h
index 9dabb547021edc..e23290e7dcc64f 100644
--- a/tools/testing/vsock/util.h
+++ b/tools/testing/vsock/util.h
@@ -51,6 +51,10 @@ struct test_opts {
 	enum test_mode mode;
 	unsigned int peer_cid;
 	unsigned int peer_port;
+	unsigned int bind_cid;
+	bool local;
+	bool to_host;
+	bool expect_to_host;
 };
 
 /* A test case definition.  Test functions must print failures to stderr and
@@ -66,6 +70,12 @@ struct test_case {
 	void (*run_server)(const struct test_opts *opts);
 
 	bool skip;
+	bool local; /* Can run without a control connection */
+
+	/* Return a skip reason or NULL after the selection barrier. Checks
+	 * that coordinate over the control channel must agree on both peers.
+	 */
+	const char *(*check)(const struct test_opts *opts);
 };
 
 void init_signals(void);
diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c
index 10b9074fb87f7f..f4ff6a42a9cb17 100644
--- a/tools/testing/vsock/vsock_test.c
+++ b/tools/testing/vsock/vsock_test.c
@@ -27,6 +27,7 @@
 #include <linux/sockios.h>
 
 #include "vsock_test_zerocopy.h"
+#include "vsock_test_dgram.h"
 #include "timeout.h"
 #include "control.h"
 #include "util.h"
@@ -2911,6 +2912,95 @@ static struct test_case test_cases[] = {
 	{
 		.name = "SO_INQ option validation",
 		.run_client = test_so_inq_validation_client,
+	},
+		.name = "SOCK_DGRAM autobind, duplicate bind and wildcard destination",
+		.run_client = test_dgram_bind,
+		.run_server = test_dgram_bind,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM queue ioctls, poll, peek, truncation and empty messages",
+		.run_client = test_dgram_receive,
+		.run_server = test_dgram_receive,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM unsupported receive flags preserve normal data",
+		.run_client = test_dgram_receive_flags,
+		.run_server = test_dgram_receive_flags,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM socket filter trims receive length",
+		.run_client = test_dgram_filter,
+		.run_server = test_dgram_filter,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM connect, disconnect and shutdown peer filtering",
+		.run_client = test_dgram_connect,
+		.run_server = test_dgram_connect,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM explicit loopback source CIDs and reply addresses",
+		.run_client = test_dgram_source_cid,
+		.run_server = test_dgram_source_cid,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM connected stream/seqpacket cannot shadow datagrams",
+		.run_client = test_dgram_bound_lookup,
+		.run_server = test_dgram_bound_lookup,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM sockmap and sockhash reject datagrams (BPF privileges)",
+		.run_client = test_dgram_sockmap,
+		.run_server = test_dgram_sockmap,
+		.skip = true,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM nested CID_HOST loopback rejection (nested VM only)",
+		.run_client = test_dgram_nested_source,
+		.run_server = test_dgram_nested_source,
+		.skip = true,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM bidirectional sizes, 64 KiB boundaries and RX refill",
+		.run_client = test_dgram_sizes,
+		.run_server = test_dgram_sizes,
+		.check = vsock_dgram_check_peer,
+	},
+	{
+		.name = "SOCK_DGRAM connected socket alternates loopback and peer routes",
+		.run_client = test_dgram_routes,
+		.run_server = test_dgram_routes,
+		.check = vsock_dgram_check_routes,
+	},
+	{
+		.name = "SOCK_DGRAM peer filtering and disconnect across transports",
+		.run_client = test_dgram_peer_filter,
+		.run_server = test_dgram_peer_filter,
+		.check = vsock_dgram_check_peer,
+	},
+	{
+		.name = "SOCK_DGRAM VMCI ioctl(SIOCINQ) payload length",
+		.run_client = test_dgram_vmci_inq,
+		.run_server = test_dgram_vmci_inq,
+		.check = vsock_dgram_check_vmci,
+		.local = true,
 	},
 	{},
 };
@@ -2957,6 +3047,26 @@ static const struct option longopts[] = {
 		.has_arg = required_argument,
 		.val = 't',
 	},
+	{
+		.name = "local",
+		.has_arg = no_argument,
+		.val = 'L',
+	},
+	{
+		.name = "bind-cid",
+		.has_arg = required_argument,
+		.val = 'b',
+	},
+	{
+		.name = "to-host",
+		.has_arg = no_argument,
+		.val = 'T',
+	},
+	{
+		.name = "expect-to-host",
+		.has_arg = no_argument,
+		.val = 'E',
+	},
 	{
 		.name = "help",
 		.has_arg = no_argument,
@@ -2972,8 +3082,10 @@ static void usage(void)
 		"  Server: vsock_test --control-port=1234 --mode=server --peer-cid=3\n"
 		"  Client: vsock_test --control-host=192.168.0.1 --control-port=1234 --mode=client --peer-cid=2\n"
 		"\n"
-		"Run vsock.ko tests.  Must be launched in both guest\n"
-		"and host.  One side must use --mode=client and\n"
+		"  Local: vsock_test --local [--pick=<test_id>]\n"
+		"\n"
+		"Run vsock.ko tests. Paired tests must be launched in both guest\n"
+		"and host. One side must use --mode=client and\n"
 		"the other side must use --mode=server.\n"
 		"\n"
 		"A TCP control socket connection is used to coordinate tests\n"
@@ -2986,8 +3098,14 @@ static void usage(void)
 		"specified with --peer-port=<port> (or the default port)\n"
 		"and the next one.\n"
 		"\n"
+		"--local runs local datagram cases without a control connection.\n"
+		"\n"
 		"Options:\n"
 		"  --help                 This help message\n"
+		"  --local                Run local datagram cases without a peer\n"
+		"  --bind-cid <cid>       Source CID for peer datagram tests [default: ANY]\n"
+		"  --to-host              Route peer datagrams through the frontend\n"
+		"  --expect-to-host       Expect the upstream datagram source flag\n"
 		"  --control-host <host>  Server IP address to connect to\n"
 		"  --control-port <port>  Server port to listen on/connect to\n"
 		"  --mode client|server   Server or client mode\n"
@@ -3011,7 +3129,10 @@ int main(int argc, char **argv)
 		.mode = TEST_MODE_UNSET,
 		.peer_cid = VMADDR_CID_ANY,
 		.peer_port = DEFAULT_PEER_PORT,
+		.bind_cid = VMADDR_CID_ANY,
 	};
+	bool picked = false;
+	size_t i;
 
 	srand(time(NULL));
 	init_signals();
@@ -3023,6 +3144,18 @@ int main(int argc, char **argv)
 			break;
 
 		switch (opt) {
+		case 'L':
+			opts.local = true;
+			break;
+		case 'b':
+			opts.bind_cid = parse_cid(optarg);
+			break;
+		case 'T':
+			opts.to_host = true;
+			break;
+		case 'E':
+			opts.expect_to_host = true;
+			break;
 		case 'H':
 			control_host = optarg;
 			break;
@@ -3055,6 +3188,7 @@ int main(int argc, char **argv)
 		case 't':
 			pick_test(test_cases, ARRAY_SIZE(test_cases) - 1,
 				  optarg);
+			picked = true;
 			break;
 		case '?':
 		default:
@@ -3062,6 +3196,22 @@ int main(int argc, char **argv)
 		}
 	}
 
+	if (optind != argc)
+		usage();
+	if (opts.local) {
+		if (opts.mode != TEST_MODE_UNSET || control_host || control_port)
+			usage();
+		if (picked) {
+			for (i = 0; test_cases[i].name; i++) {
+				if (!test_cases[i].skip && !test_cases[i].local) {
+					fprintf(stderr, "test %zu requires paired mode\n", i);
+					exit(EXIT_FAILURE);
+				}
+			}
+		}
+		run_tests(test_cases, &opts);
+		return EXIT_SUCCESS;
+	}
 	if (!control_port)
 		usage();
 	if (opts.mode == TEST_MODE_UNSET)
diff --git a/tools/testing/vsock/vsock_test_dgram.c b/tools/testing/vsock/vsock_test_dgram.c
new file mode 100644
index 00000000000000..d818f43176dc9f
--- /dev/null
+++ b/tools/testing/vsock/vsock_test_dgram.c
@@ -0,0 +1,809 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Datagram feature tests for vsock. */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/bpf.h>
+#include <linux/filter.h>
+#include <linux/sockios.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/syscall.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "control.h"
+#include "timeout.h"
+#include "vsock_test_dgram.h"
+
+#define MAX_DGRAM (64 * 1024)
+
+static void fail(const char *fmt, ...)
+{
+	va_list ap;
+
+	fprintf(stderr, "\nFAIL: ");
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	fputc('\n', stderr);
+	exit(EXIT_FAILURE);
+}
+
+static void check(int ret, const char *operation)
+{
+	if (ret < 0)
+		fail("%s: %s", operation, strerror(errno));
+}
+
+static void expect_error(ssize_t ret, int error, const char *operation)
+{
+	if (ret != -1 || errno != error)
+		fail("%s: expected %s, got %zd (%s)", operation,
+		     strerror(error), ret, ret == -1 ? strerror(errno) : "success");
+}
+
+static int64_t now_ms(void)
+{
+	struct timespec ts;
+
+	check(clock_gettime(CLOCK_MONOTONIC, &ts), "clock_gettime");
+	return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
+}
+
+static short poll_fd(int fd, short events, int milliseconds)
+{
+	struct pollfd pfd = { .fd = fd, .events = events };
+	int ret;
+
+	do {
+		ret = poll(&pfd, 1, milliseconds);
+	} while (ret < 0 && errno == EINTR);
+	check(ret, "poll");
+	if (pfd.revents & (POLLERR | POLLNVAL))
+		fail("unexpected poll events %#x", pfd.revents);
+	return pfd.revents;
+}
+
+static void wait_readable(int fd)
+{
+	if (!(poll_fd(fd, POLLIN, TIMEOUT * 1000) & POLLIN))
+		fail("waiting for socket readability timed out");
+}
+
+static struct sockaddr_vm address(unsigned int cid, unsigned int port)
+{
+	return (struct sockaddr_vm) {
+		.svm_family = AF_VSOCK,
+		.svm_cid = cid,
+		.svm_port = port,
+	};
+}
+
+static struct sockaddr_vm sock_name(int fd, bool peer)
+{
+	struct sockaddr_vm addr;
+	socklen_t len = sizeof(addr);
+	int ret;
+
+	if (peer)
+		ret = getpeername(fd, (struct sockaddr *)&addr, &len);
+	else
+		ret = getsockname(fd, (struct sockaddr *)&addr, &len);
+	check(ret, peer ? "getpeername" : "getsockname");
+	if (len != sizeof(addr) || addr.svm_family != AF_VSOCK)
+		fail("invalid socket address");
+	return addr;
+}
+
+static unsigned int local_cid(void)
+{
+	unsigned int cid;
+	int fd;
+
+	fd = open("/dev/vsock", O_RDONLY | O_CLOEXEC);
+	check(fd, "open /dev/vsock");
+	check(ioctl(fd, IOCTL_VM_SOCKETS_GET_LOCAL_CID, &cid), "get local CID");
+	close(fd);
+	if (cid == VMADDR_CID_ANY || cid <= VMADDR_CID_LOCAL)
+		fail("a virtio or vhost transport is required (local CID %u)", cid);
+	return cid;
+}
+
+static void bind_fd(int fd, unsigned int cid, unsigned int port)
+{
+	struct sockaddr_vm addr = address(cid, port);
+
+	check(bind(fd, (struct sockaddr *)&addr, sizeof(addr)), "bind");
+}
+
+static int dgram_socket(void)
+{
+	int fd = socket(AF_VSOCK, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+	int rcvbuf = 1024 * 1024;
+
+	check(fd, "socket(AF_VSOCK, SOCK_DGRAM): check transport/feature support");
+	check(setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)), "SO_RCVBUF");
+	return fd;
+}
+
+enum dgram_capability {
+	DGRAM_SOCKET = 1 << 0,
+	DGRAM_VIRTIO = 1 << 1,
+	DGRAM_VMCI = 1 << 2,
+	DGRAM_LOOPBACK = 1 << 3,
+	DGRAM_PEER = 1 << 4,
+};
+
+static bool dgram_unavailable(int error)
+{
+	return error == EAFNOSUPPORT || error == EPROTONOSUPPORT ||
+	       error == ESOCKTNOSUPPORT || error == ENODEV;
+}
+
+static unsigned int dgram_capabilities(const struct test_opts *opts,
+				       unsigned int needs)
+{
+	unsigned int caps = DGRAM_SOCKET;
+	struct sockaddr_vm peer;
+	int fd, local_fd;
+
+	fd = socket(AF_VSOCK, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+	if (fd < 0) {
+		if (dgram_unavailable(errno))
+			return 0;
+		check(fd, "probe datagram socket");
+	}
+	/* VMCI registers the dedicated datagram transport when loaded. */
+	if (!access("/sys/module/vmw_vsock_vmci_transport", F_OK))
+		caps |= DGRAM_VMCI;
+	else
+		caps |= DGRAM_VIRTIO;
+
+	if ((needs & DGRAM_PEER) && (caps & DGRAM_VIRTIO)) {
+		bind_fd(fd, opts->bind_cid, VMADDR_PORT_ANY);
+		peer = address(opts->peer_cid, opts->peer_port);
+		if (opts->to_host)
+			peer.svm_flags = VMADDR_FLAG_TO_HOST;
+		if (!connect(fd, (struct sockaddr *)&peer, sizeof(peer)))
+			caps |= DGRAM_PEER;
+		else if (!dgram_unavailable(errno) && errno != EINVAL)
+			check(-1, "probe peer datagram route");
+	}
+	close(fd);
+
+	if ((needs & DGRAM_LOOPBACK) && (caps & DGRAM_VIRTIO)) {
+		/* Binding a stream socket to CID_LOCAL probes registration of
+		 * the loopback transport without sending a packet to the host.
+		 */
+		local_fd = vsock_bind_try(VMADDR_CID_LOCAL, VMADDR_PORT_ANY, SOCK_STREAM);
+		if (local_fd >= 0) {
+			caps |= DGRAM_LOOPBACK;
+			close(local_fd);
+		} else if (errno != EADDRNOTAVAIL) {
+			check(local_fd, "probe loopback transport");
+		}
+	}
+	return caps;
+}
+
+static const char *dgram_check(const struct test_opts *opts, unsigned int needs)
+{
+	unsigned int local = dgram_capabilities(opts, needs);
+	unsigned int both = local;
+
+	/* run_tests() has already synchronized explicit --pick/--skip
+	 * selection. Both peers can now safely exchange capabilities.
+	 */
+	if (!opts->local) {
+		control_writeulong(local);
+		both &= control_readulong();
+	}
+	if (!(both & DGRAM_SOCKET))
+		return local & DGRAM_SOCKET ?
+			"datagrams unavailable at peer" : "datagrams unavailable locally";
+	if ((needs & DGRAM_VIRTIO) && !(both & DGRAM_VIRTIO))
+		return "requires virtio/vhost datagram transports on both peers";
+	if ((needs & DGRAM_VMCI) && !(both & DGRAM_VMCI))
+		return "requires VMCI datagram transports on both peers";
+	if ((needs & DGRAM_LOOPBACK) && !(both & DGRAM_LOOPBACK))
+		return "load vsock_loopback on both peers";
+	if ((needs & DGRAM_PEER) && !(both & DGRAM_PEER))
+		return "peer route does not support datagrams";
+	return NULL;
+}
+
+const char *vsock_dgram_check_local(const struct test_opts *opts)
+{
+	return dgram_check(opts, DGRAM_VIRTIO | DGRAM_LOOPBACK);
+}
+
+const char *vsock_dgram_check_peer(const struct test_opts *opts)
+{
+	return dgram_check(opts, DGRAM_VIRTIO | DGRAM_PEER);
+}
+
+const char *vsock_dgram_check_routes(const struct test_opts *opts)
+{
+	return dgram_check(opts, DGRAM_VIRTIO | DGRAM_PEER | DGRAM_LOOPBACK);
+}
+
+const char *vsock_dgram_check_vmci(const struct test_opts *opts)
+{
+	return dgram_check(opts, DGRAM_VMCI);
+}
+
+static int bound_socket(unsigned int cid)
+{
+	int fd = dgram_socket();
+
+	bind_fd(fd, cid, VMADDR_PORT_ANY);
+	return fd;
+}
+
+static struct sockaddr_vm local_address(int fd)
+{
+	struct sockaddr_vm addr = sock_name(fd, false);
+
+	addr.svm_cid = VMADDR_CID_LOCAL;
+	return addr;
+}
+
+static void connect_fd(int fd, const struct sockaddr_vm *addr)
+{
+	check(connect(fd, (const struct sockaddr *)addr, sizeof(*addr)), "connect");
+}
+
+static void disconnect_fd(int fd)
+{
+	struct sockaddr_vm addr = { .svm_family = AF_UNSPEC };
+	socklen_t len = sizeof(addr);
+
+	check(connect(fd, (struct sockaddr *)&addr, sizeof(addr)), "disconnect");
+	expect_error(getpeername(fd, (struct sockaddr *)&addr, &len),
+		     ENOTCONN, "getpeername after disconnect");
+}
+
+static int queue_bytes(int fd, unsigned long op)
+{
+	int bytes;
+
+	check(ioctl(fd, op, &bytes), "queue ioctl");
+	if (bytes < 0)
+		fail("negative queue length: %d", bytes);
+	return bytes;
+}
+
+static void expect_inq(int fd, int expected)
+{
+	int actual = queue_bytes(fd, SIOCINQ);
+
+	if (actual != expected)
+		fail("SIOCINQ: expected %d, got %d", expected, actual);
+}
+
+static void wait_sent(int fd)
+{
+	int64_t deadline = now_ms() + TIMEOUT * 1000;
+
+	while (queue_bytes(fd, SIOCOUTQ)) {
+		if (now_ms() >= deadline)
+			fail("SIOCOUTQ did not return to zero");
+		usleep(1000);
+	}
+}
+
+static void send_packet(int fd, const struct sockaddr_vm *addr,
+			const void *buf, size_t len)
+{
+	int64_t deadline = now_ms() + TIMEOUT * 1000;
+	ssize_t ret;
+
+	for (;;) {
+		ret = sendto(fd, buf, len, MSG_DONTWAIT | MSG_NOSIGNAL,
+			     (const struct sockaddr *)addr, addr ? sizeof(*addr) : 0);
+		if (ret >= 0)
+			break;
+		if (errno != EAGAIN && errno != EINTR)
+			fail("sendto(%zu bytes): %s", len, strerror(errno));
+		if (now_ms() >= deadline)
+			fail("sendto(%zu bytes) timed out", len);
+		poll_fd(fd, POLLOUT, 1);
+	}
+	if (ret != len)
+		fail("partial datagram send: %zd/%zu", ret, len);
+}
+
+static struct sockaddr_vm recv_packet(int fd, void *buf, size_t capacity,
+				      int flags, size_t expected, bool truncated)
+{
+	struct sockaddr_vm src = { 0 };
+	struct iovec iov = { .iov_base = buf, .iov_len = capacity };
+	struct msghdr msg = {
+		.msg_name = &src,
+		.msg_namelen = sizeof(src),
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	ssize_t ret;
+
+	wait_readable(fd);
+	ret = recvmsg(fd, &msg, flags | MSG_DONTWAIT);
+	check(ret, "recvmsg");
+	if (ret != expected || !!(msg.msg_flags & MSG_TRUNC) != truncated)
+		fail("recvmsg: expected %zu bytes, trunc=%d; got %zd, flags=%#x",
+		     expected, truncated, ret, msg.msg_flags);
+	if (msg.msg_namelen != sizeof(src) || src.svm_family != AF_VSOCK)
+		fail("invalid recvmsg source address");
+	return src;
+}
+
+static void expect_empty(int fd)
+{
+	char byte;
+
+	if (poll_fd(fd, POLLIN, 0) & POLLIN)
+		fail("unexpected queued datagram");
+	expect_error(recv(fd, &byte, 1, MSG_DONTWAIT), EAGAIN, "empty recv");
+	expect_inq(fd, 0);
+}
+
+void test_dgram_bind(const struct test_opts *opts)
+{
+	int rx = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm dest = local_address(rx);
+	int tx = dgram_socket();
+	int other = dgram_socket();
+	struct sockaddr_vm src;
+	char byte;
+
+	dest.svm_cid = VMADDR_CID_ANY;
+	expect_error(bind(other, (struct sockaddr *)&dest, sizeof(dest)),
+		     EADDRINUSE, "duplicate bind");
+	send_packet(tx, &dest, "A", 1);
+	src = recv_packet(rx, &byte, 1, 0, 1, false);
+	if (byte != 'A' || src.svm_port != sock_name(tx, false).svm_port ||
+	    src.svm_cid != VMADDR_CID_LOCAL || src.svm_port < 1024)
+		fail("autobind or wildcard destination returned an invalid source");
+	close(rx);
+	bind_fd(other, VMADDR_CID_ANY, dest.svm_port);
+	close(other);
+	close(tx);
+}
+
+void test_dgram_receive(const struct test_opts *opts)
+{
+	int rx = bound_socket(VMADDR_CID_ANY);
+	int tx = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm dest = local_address(rx);
+	char buf[80];
+	char first[31], second[79];
+
+	memset(first, 'A', sizeof(first));
+	memset(second, 'B', sizeof(second));
+	expect_empty(rx);
+	if (!(poll_fd(tx, POLLOUT, 0) & POLLOUT))
+		fail("new socket is not writable");
+	if (queue_bytes(tx, SIOCOUTQ))
+		fail("new socket has pending output");
+	send_packet(tx, &dest, first, sizeof(first));
+	send_packet(tx, &dest, second, sizeof(second));
+	wait_readable(rx);
+	expect_inq(rx, sizeof(first));
+	recv_packet(rx, buf, 5, MSG_PEEK, 5, true);
+	if (memcmp(buf, first, 5))
+		fail("MSG_PEEK payload mismatch");
+	expect_inq(rx, sizeof(first));
+	recv_packet(rx, buf, 5, MSG_TRUNC, sizeof(first), true);
+	wait_readable(rx);
+	expect_inq(rx, sizeof(second));
+	recv_packet(rx, buf, sizeof(buf), 0, sizeof(second), false);
+	if (memcmp(buf, second, sizeof(second)))
+		fail("datagram boundary or payload mismatch");
+	send_packet(tx, &dest, "", 0);
+	wait_readable(rx);
+	expect_inq(rx, 0);
+	recv_packet(rx, buf, sizeof(buf), MSG_PEEK, 0, false);
+	recv_packet(rx, buf, sizeof(buf), 0, 0, false);
+	send_packet(tx, &dest, first, sizeof(first));
+	recv_packet(rx, buf, 0, MSG_TRUNC, sizeof(first), true);
+	expect_empty(rx);
+	wait_sent(tx);
+	close(tx);
+	close(rx);
+}
+
+void test_dgram_receive_flags(const struct test_opts *opts)
+{
+	int rx = bound_socket(VMADDR_CID_ANY);
+	int tx = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm dest = local_address(rx);
+	const int flags[] = { MSG_OOB, MSG_ERRQUEUE };
+	char byte;
+	size_t i;
+
+	send_packet(tx, &dest, "A", 1);
+	wait_readable(rx);
+	for (i = 0; i < ARRAY_SIZE(flags); i++) {
+		expect_error(recv(rx, &byte, 1, flags[i] | MSG_DONTWAIT),
+			     EOPNOTSUPP, "unsupported receive flag");
+		expect_inq(rx, 1);
+	}
+	recv_packet(rx, &byte, 1, 0, 1, false);
+	if (byte != 'A')
+		fail("unsupported flags consumed the normal packet");
+	close(tx);
+	close(rx);
+}
+
+void test_dgram_vmci_inq(const struct test_opts *opts)
+{
+	int rx = bound_socket(VMADDR_CID_ANY);
+	int tx = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm dest = sock_name(rx, false);
+	char first[31], second[79], buf[80];
+
+	memset(first, 'A', sizeof(first));
+	memset(second, 'B', sizeof(second));
+	/* CID_ANY is resolved by the dedicated transport to its local CID. */
+	dest.svm_cid = VMADDR_CID_ANY;
+	send_packet(tx, &dest, first, sizeof(first));
+	send_packet(tx, &dest, second, sizeof(second));
+	wait_readable(rx);
+	expect_inq(rx, sizeof(first));
+	recv_packet(rx, buf, sizeof(buf), 0, sizeof(first), false);
+	if (memcmp(buf, first, sizeof(first)))
+		fail("VMCI first payload mismatch");
+	wait_readable(rx);
+	expect_inq(rx, sizeof(second));
+	recv_packet(rx, buf, sizeof(buf), 0, sizeof(second), false);
+	if (memcmp(buf, second, sizeof(second)))
+		fail("VMCI second payload mismatch");
+	send_packet(tx, &dest, "", 0);
+	wait_readable(rx);
+	expect_inq(rx, 0);
+	recv_packet(rx, buf, sizeof(buf), 0, 0, false);
+	expect_empty(rx);
+	close(tx);
+	close(rx);
+}
+
+void test_dgram_filter(const struct test_opts *opts)
+{
+	struct sock_filter insn[] = { BPF_STMT(BPF_RET | BPF_K, 7) };
+	struct sock_fprog prog = { .len = ARRAY_SIZE(insn), .filter = insn };
+	int rx = bound_socket(VMADDR_CID_ANY);
+	int tx = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm dest = local_address(rx);
+	char data[100], buf[100];
+
+	memset(data, 'F', sizeof(data));
+	check(setsockopt(rx, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)),
+	      "SO_ATTACH_FILTER");
+	send_packet(tx, &dest, data, sizeof(data));
+	wait_readable(rx);
+	expect_inq(rx, 7);
+	recv_packet(rx, buf, 1, MSG_PEEK | MSG_TRUNC, 7, true);
+	expect_inq(rx, 7);
+	memset(buf, 0, sizeof(buf));
+	recv_packet(rx, buf, sizeof(buf), MSG_TRUNC, 7, false);
+	if (memcmp(buf, data, 7) || buf[7])
+		fail("filtered receive copied beyond the filtered payload");
+	send_packet(tx, &dest, data, sizeof(data));
+	recv_packet(rx, buf, 1, MSG_TRUNC, 7, true);
+	expect_empty(rx);
+	close(tx);
+	close(rx);
+}
+
+static void check_peer_filter(int rx, int allowed, int stray)
+{
+	struct sockaddr_vm dest = local_address(rx);
+	char byte;
+
+	/* Both packets use the same ordered loopback worker. */
+	send_packet(stray, &dest, "B", 1);
+	send_packet(allowed, &dest, "A", 1);
+	recv_packet(rx, &byte, 1, 0, 1, false);
+	if (byte != 'A')
+		fail("connected socket accepted a different peer");
+	expect_empty(rx);
+}
+
+void test_dgram_connect(const struct test_opts *opts)
+{
+	int rx = bound_socket(VMADDR_CID_ANY);
+	int allowed = bound_socket(VMADDR_CID_ANY);
+	int stray = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm peer = local_address(allowed);
+	struct sockaddr_vm dest = local_address(rx);
+	char byte;
+
+	connect_fd(rx, &peer);
+	check_peer_filter(rx, allowed, stray);
+	disconnect_fd(rx);
+	send_packet(stray, &dest, "B", 1);
+	recv_packet(rx, &byte, 1, 0, 1, false);
+	if (byte != 'B')
+		fail("disconnect did not remove peer filtering");
+	connect_fd(rx, &peer);
+	check(shutdown(rx, SHUT_WR), "shutdown SHUT_WR");
+	if (poll_fd(rx, POLLOUT, 0) & POLLOUT)
+		fail("write-shutdown socket is still writable");
+	check_peer_filter(rx, allowed, stray);
+	check(shutdown(rx, SHUT_RD), "shutdown SHUT_RD");
+	if (!(poll_fd(rx, POLLIN | POLLRDHUP, 0) & POLLRDHUP))
+		fail("read shutdown did not report POLLRDHUP");
+	close(stray);
+	close(allowed);
+	close(rx);
+}
+
+void test_dgram_source_cid(const struct test_opts *opts)
+{
+	unsigned int cids[] = { VMADDR_CID_LOCAL, local_cid() };
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(cids); i++) {
+		int rx = bound_socket(VMADDR_CID_ANY);
+		int tx = bound_socket(cids[i]);
+		struct sockaddr_vm dest = local_address(rx);
+		struct sockaddr_vm src;
+		char byte;
+
+		send_packet(tx, &dest, "A", 1);
+		src = recv_packet(rx, &byte, 1, 0, 1, false);
+		if (src.svm_cid != cids[i] || src.svm_flags ||
+		    src.svm_port != sock_name(tx, false).svm_port)
+			fail("explicit loopback source CID %u was not preserved", cids[i]);
+		send_packet(rx, &src, "R", 1);
+		recv_packet(tx, &byte, 1, 0, 1, false);
+		if (byte != 'R')
+			fail("reply to explicit source CID failed");
+		close(tx);
+		close(rx);
+	}
+}
+
+void test_dgram_bound_lookup(const struct test_opts *opts)
+{
+	const int types[] = { SOCK_STREAM, SOCK_SEQPACKET };
+	unsigned int cid = local_cid();
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(types); i++) {
+		int listener = vsock_bind(VMADDR_CID_ANY, VMADDR_PORT_ANY, types[i]);
+		int stream = vsock_bind(VMADDR_CID_LOCAL, VMADDR_PORT_ANY, types[i]);
+		struct sockaddr_vm listen_addr = local_address(listener);
+		struct sockaddr_vm src = sock_name(stream, false);
+		struct sockaddr_vm dest;
+		int accepted, tx, rx;
+		char byte;
+
+		check(listen(listener, 1), "listen");
+		if (vsock_connect_fd(stream, listen_addr.svm_cid, listen_addr.svm_port))
+			fail("%s loopback connect: %s",
+			     types[i] == SOCK_STREAM ? "stream" : "seqpacket", strerror(errno));
+		wait_readable(listener);
+		accepted = accept4(listener, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);
+		check(accepted, "accept");
+		if (sock_name(accepted, true).svm_cid != VMADDR_CID_LOCAL ||
+		    sock_name(accepted, true).svm_port != src.svm_port)
+			fail("unexpected accepted loopback peer");
+		close(listener);
+		rx = dgram_socket();
+		bind_fd(rx, cid, src.svm_port);
+		dest = sock_name(rx, false);
+		tx = dgram_socket();
+		/* Reuse the listener's port for the datagram source. The receive
+		 * socket uses the stream client's port at another local CID.
+		 * Connected lookup ignores the destination CID, so it would find
+		 * the stream client before the bound datagram socket.
+		 */
+		bind_fd(tx, VMADDR_CID_LOCAL, listen_addr.svm_port);
+		send_packet(tx, &dest, "D", 1);
+		recv_packet(rx, &byte, 1, 0, 1, false);
+		if (byte != 'D' || (poll_fd(stream, POLLIN, 0) & POLLIN) ||
+		    (poll_fd(accepted, POLLIN, 0) & POLLIN))
+			fail("connected socket shadowed the reused datagram port");
+		send_byte(stream, 1, 0);
+		wait_readable(accepted);
+		recv_byte(accepted, 1, 0);
+		send_byte(accepted, 1, 0);
+		wait_readable(stream);
+		recv_byte(stream, 1, 0);
+		close(tx);
+		close(rx);
+		close(accepted);
+		close(stream);
+	}
+}
+
+void test_dgram_sockmap(const struct test_opts *opts)
+{
+	const int types[] = { BPF_MAP_TYPE_SOCKMAP, BPF_MAP_TYPE_SOCKHASH };
+	int fd = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm addr = local_address(fd);
+	uint64_t value = fd;
+	uint32_t key = 0;
+	size_t i;
+
+	connect_fd(fd, &addr);
+	for (i = 0; i < ARRAY_SIZE(types); i++) {
+		union bpf_attr attr = {
+			.map_type = types[i],
+			.key_size = sizeof(key),
+			.value_size = sizeof(value),
+			.max_entries = 1,
+		};
+		int map = syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
+
+		check(map, "BPF_MAP_CREATE (requires BPF privileges and CONFIG_BPF_SYSCALL)");
+		memset(&attr, 0, sizeof(attr));
+		attr.map_fd = map;
+		attr.key = (uintptr_t)&key;
+		attr.value = (uintptr_t)&value;
+		attr.flags = BPF_ANY;
+		expect_error(syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)),
+			     EOPNOTSUPP, "datagram sockmap admission");
+		close(map);
+	}
+	close(fd);
+}
+
+void test_dgram_nested_source(const struct test_opts *opts)
+{
+	int rx, tx;
+	struct sockaddr_vm dest;
+
+	if (local_cid() <= VMADDR_CID_HOST)
+		fail("this test requires both a guest frontend and vhost_vsock");
+	rx = bound_socket(VMADDR_CID_LOCAL);
+	tx = bound_socket(VMADDR_CID_HOST);
+	dest = local_address(rx);
+	expect_error(sendto(tx, "A", 1, MSG_NOSIGNAL,
+			    (struct sockaddr *)&dest, sizeof(dest)),
+		     EADDRNOTAVAIL, "nested CID_HOST loopback send");
+	expect_error(connect(tx, (struct sockaddr *)&dest, sizeof(dest)),
+		     EADDRNOTAVAIL, "nested CID_HOST loopback connect");
+	if (queue_bytes(tx, SIOCOUTQ))
+		fail("rejected route left pending output");
+	expect_empty(rx);
+	close(tx);
+	close(rx);
+}
+
+static int peer_socket(const struct test_opts *opts, struct sockaddr_vm *peer)
+{
+	int fd = bound_socket(opts->bind_cid);
+
+	control_writeulong(sock_name(fd, false).svm_port);
+	*peer = address(opts->peer_cid, control_readulong());
+	if (opts->to_host)
+		peer->svm_flags = VMADDR_FLAG_TO_HOST;
+	return fd;
+}
+
+static void check_source(const struct test_opts *opts,
+			 const struct sockaddr_vm *peer, const struct sockaddr_vm *src)
+{
+	bool upstream = opts->expect_to_host || opts->peer_cid == VMADDR_CID_HOST;
+
+	if (src->svm_cid != peer->svm_cid || src->svm_port != peer->svm_port ||
+	    src->svm_flags != (upstream ? VMADDR_FLAG_TO_HOST : 0))
+		fail("source expected %u:%u flags=%u, got %u:%u flags=%u",
+		     peer->svm_cid, peer->svm_port, upstream ? VMADDR_FLAG_TO_HOST : 0,
+		     src->svm_cid, src->svm_port, src->svm_flags);
+}
+
+static void fill_payload(char *buf, size_t len, unsigned int seed)
+{
+	size_t i;
+
+	for (i = 0; i < len; i++)
+		buf[i] = (i * 131 + seed) & 0xff;
+}
+
+void test_dgram_sizes(const struct test_opts *opts)
+{
+	const size_t sizes[] = { 0, 1, 7, 255, 4095, 4096, 4097,
+				8192, 32768, 65535, MAX_DGRAM };
+	char *data = malloc(MAX_DGRAM + 1);
+	char *buf = malloc(MAX_DGRAM + 1);
+	struct sockaddr_vm peer, src;
+	unsigned int round;
+	int fd;
+	size_t i;
+
+	if (!data || !buf)
+		fail("malloc");
+	fd = peer_socket(opts, &peer);
+	fill_payload(data, MAX_DGRAM + 1, 0);
+	expect_error(sendto(fd, data, MAX_DGRAM + 1, MSG_NOSIGNAL,
+			    (struct sockaddr *)&peer, sizeof(peer)),
+		     EMSGSIZE, "oversized datagram");
+	if (queue_bytes(fd, SIOCOUTQ))
+		fail("oversized datagram left pending output");
+	/* More than a default RX ring's worth, including on small rings. */
+	for (round = 0; round < 32; round++) {
+		for (i = 0; i < ARRAY_SIZE(sizes); i++) {
+			fill_payload(data, sizes[i], round * ARRAY_SIZE(sizes) + i);
+			if (opts->mode == TEST_MODE_CLIENT)
+				send_packet(fd, &peer, data, sizes[i]);
+			src = recv_packet(fd, buf, MAX_DGRAM + 1, 0, sizes[i], false);
+			check_source(opts, &peer, &src);
+			if (memcmp(data, buf, sizes[i]))
+				fail("payload mismatch: size %zu, round %u", sizes[i], round);
+			if (opts->mode == TEST_MODE_SERVER)
+				send_packet(fd, &src, buf, sizes[i]);
+		}
+	}
+	wait_sent(fd);
+	expect_empty(fd);
+	close(fd);
+	free(buf);
+	free(data);
+}
+
+void test_dgram_routes(const struct test_opts *opts)
+{
+	struct sockaddr_vm peer, src, stored, local;
+	int fd = peer_socket(opts, &peer);
+	int aux = bound_socket(VMADDR_CID_ANY);
+	unsigned int round;
+	char byte;
+
+	connect_fd(fd, &peer);
+	stored = sock_name(fd, true);
+	local = local_address(aux);
+	for (round = 0; round < 32; round++) {
+		send_packet(fd, &local, "L", 1);
+		recv_packet(aux, &byte, 1, 0, 1, false);
+		if (byte != 'L')
+			fail("sendto on a connected socket did not use loopback");
+		src = sock_name(fd, true);
+		if (src.svm_cid != stored.svm_cid || src.svm_port != stored.svm_port ||
+		    src.svm_flags != stored.svm_flags)
+			fail("per-message route changed the connected peer");
+		send_packet(fd, NULL, "P", 1);
+		src = recv_packet(fd, &byte, 1, 0, 1, false);
+		check_source(opts, &peer, &src);
+		if (byte != 'P')
+			fail("connected send did not retain the peer route");
+	}
+	wait_sent(fd);
+	close(aux);
+	close(fd);
+}
+
+void test_dgram_peer_filter(const struct test_opts *opts)
+{
+	struct sockaddr_vm peer, src;
+	int fd = peer_socket(opts, &peer);
+	int stray = bound_socket(opts->bind_cid);
+	char byte;
+
+	connect_fd(fd, &peer);
+	control_writeln("CONNECTED");
+	control_expectln("CONNECTED");
+	send_packet(stray, &peer, "B", 1);
+	send_packet(fd, NULL, "A", 1);
+	src = recv_packet(fd, &byte, 1, 0, 1, false);
+	check_source(opts, &peer, &src);
+	if (byte != 'A')
+		fail("connected socket accepted a different peer port");
+	disconnect_fd(fd);
+	control_writeln("DISCONNECTED");
+	control_expectln("DISCONNECTED");
+	send_packet(stray, &peer, "B", 1);
+	src = recv_packet(fd, &byte, 1, 0, 1, false);
+	if (byte != 'B' || src.svm_cid != peer.svm_cid || src.svm_port == peer.svm_port)
+		fail("disconnected socket did not accept the new peer");
+	close(stray);
+	close(fd);
+}
diff --git a/tools/testing/vsock/vsock_test_dgram.h b/tools/testing/vsock/vsock_test_dgram.h
new file mode 100644
index 00000000000000..0dec74ef5f4685
--- /dev/null
+++ b/tools/testing/vsock/vsock_test_dgram.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef VSOCK_TEST_DGRAM_H
+#define VSOCK_TEST_DGRAM_H
+
+#include "util.h"
+
+const char *vsock_dgram_check_local(const struct test_opts *opts);
+const char *vsock_dgram_check_peer(const struct test_opts *opts);
+const char *vsock_dgram_check_routes(const struct test_opts *opts);
+const char *vsock_dgram_check_vmci(const struct test_opts *opts);
+
+void test_dgram_bind(const struct test_opts *opts);
+void test_dgram_receive(const struct test_opts *opts);
+void test_dgram_receive_flags(const struct test_opts *opts);
+void test_dgram_filter(const struct test_opts *opts);
+void test_dgram_connect(const struct test_opts *opts);
+void test_dgram_source_cid(const struct test_opts *opts);
+void test_dgram_bound_lookup(const struct test_opts *opts);
+void test_dgram_sockmap(const struct test_opts *opts);
+void test_dgram_nested_source(const struct test_opts *opts);
+void test_dgram_sizes(const struct test_opts *opts);
+void test_dgram_routes(const struct test_opts *opts);
+void test_dgram_peer_filter(const struct test_opts *opts);
+void test_dgram_vmci_inq(const struct test_opts *opts);
+
+#endif /* VSOCK_TEST_DGRAM_H */

3/2 2026/10/29 04:41 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 33514
							output: 55
							thoughts: 898
						

Thoughts:
**Analysis of the Virtio-VSOCK SOCK_DGRAM Patch**

Okay, so I've got a Linux kernel patch series for AMD64 to look over. It seems we're dealing with the addition of `SOCK_DGRAM` support for virtio-vsock, vhost-vsock, and vsock loopback. The patch modifies a significant chunk of code related to the AF_VSOCK socket family, which immediately grabs my attention.

My initial assessment is that this touches core networking, which is precisely the kind of area that warrants careful scrutiny. The files affected – `drivers/vhost/vsock.c`, the various include files for virtio_vsock and AF_VSOCK, and the implementations within `net/vmw_vsock` – paint a clear picture. We're talking about fundamental socket functionality, and the integration of these virtual socket transports.

The fact that `vsock_loopback` utilizes `virtio_transport_common.c` is particularly interesting. Any user, regardless of whether they are in a virtual environment, can now leverage this new functionality via `socket(AF_VSOCK, SOCK_DGRAM, 0)`.

Specifically, I'm noting the definition in `vsock_loopback.c`: `static struct virtio_transport loopback_transport = { ... .dgram_transport_match = virtio_transport_dgram_transport_match ... }`. And the interaction described in `af_vsock.c`, where `vsock_dgram_get_transport` routes to `transport_local` (aka loopback) when `vsock_use_local_transport(cid)` is true. This establishes a clear connection between the high-level AF_VSOCK socket layer and the virtual transport implementation.

My next thought is about fuzzing. Is this functionality reachable in standard syzkaller environments, specifically QEMU on amd64? Thankfully, yes! AF_VSOCK is commonly targeted by syzkaller. There are already syzlang descriptions for socket(AF_VSOCK, ...), bind, connect, sendto, recvfrom, ioctls, etc., so this new functionality should be readily testable with existing fuzzing infrastructure.

Given the patch's touchpoints within core networking and virtual buses (virtio, sockets), it squarely fits the criteria for being "WORTH FUZZING".  The addition of `SOCK_DGRAM` support inherently modifies core networking logic.

Now, I'll need to check the Kconfig dependencies and symbols related to vsock, virtio_vsock, vhost_vsock, and vsock_loopback. Using `grepper` or by checking Kconfig files for `net/vmw_vsock/Kconfig` and `drivers/vhost/Kconfig`, I can get a clear picture of any dependencies that might affect the reachability or configuration of this new functionality. This will provide further context and ensure that the code is adequately tested under various configurations.



4/2 2026/10/29 04:41 tool grepper
0m Args:
{
  "Expression": "config (VSOCK|VHOST_VSOCK|VIRTIO_VSOCK)",
  "PathPrefixes": [
    "net/vmw_vsock/",
    "drivers/vhost/"
  ]
}

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-5-\nnet/vmw_vsock/Kconfig:6:config VSOCKETS\nnet/vmw_vsock/Kconfig-7-\ttristate \"Virtual Socket protocol\"\n--\nnet/vmw_vsock/Kconfig-18-\nnet/vmw_vsock/Kconfig:19:config VSOCKETS_DIAG\nnet/vmw_vsock/Kconfig-20-\ttristate \"Virtual Sockets monitoring interface\"\n--\nnet/vmw_vsock/Kconfig-28-\nnet/vmw_vsock/Kconfig:29:config VSOCKETS_LOOPBACK\nnet/vmw_vsock/Kconfig-30-\ttristate \"Virtual Sockets loopback transport\"\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/29 04:41 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 35535
							output: 36
							thoughts: 24
						

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

**Analyzing the Dependency Landscape in `net/vmw_vsock/Kconfig`**

Alright, I'm thinking, before I go any further, I should dive right into `net/vmw_vsock/Kconfig`. I need to get a clear picture of the conditional dependencies surrounding this particular area. I'm going to pinpoint those specific lines, zoom in on them, and see what the build system needs to be in place for this part of the code to even *exist*, let alone function correctly. Understanding the interconnections, the prerequisites, that's crucial before I get any deeper into whatever the current issue is. That file is the key to understanding the gatekeepers here. Let's see what's what.



6/2 2026/10/29 04:41 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/Kconfig",
  "FirstLine": 1,
  "LineCount": 90
}

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  81:\t\n  82:\t\t  To compile this driver as a module, choose M here: the module will be\n  83:\t\t  called hv_sock. If unsure, say N.\n"
}

7/2 2026/10/29 04:41 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 36888
							output: 34
							thoughts: 20
						

Thoughts:
Here's my summary, as if I were thinking these thoughts:

**Examining Vhost Driver Configuration**

Okay, so I'm looking into this issue and need to zero in on the vhost configuration. My immediate thought is to check the `drivers/vhost/Kconfig` file. I'm going to focus around line 56. That should be a good place to start digging into the options and dependencies related to the vhost driver configuration. Let's see what's defined there.



8/2 2026/10/29 04:41 tool read-file
0m Args:
{
  "File": "drivers/vhost/Kconfig",
  "FirstLine": 50,
  "LineCount": 30
}

Results:
{
  "Contents": "  50:\t\tselect SG_POOL\n  51:\t\tdefault n\n  52:\t\thelp\n  53:\t\tSay M here to enable the vhost_scsi TCM fabric module\n  54:\t\tfor use with virtio-scsi guests\n  55:\t\n  56:\tconfig VHOST_VSOCK\n  57:\t\ttristate \"vhost virtio-vsock driver\"\n  58:\t\tdepends on VSOCKETS \u0026\u0026 EVENTFD\n  59:\t\tselect VHOST\n  60:\t\tselect VIRTIO_VSOCKETS_COMMON\n  61:\t\tdefault n\n  62:\t\thelp\n  63:\t\tThis kernel module can be loaded in the host kernel to provide AF_VSOCK\n  64:\t\tsockets for communicating with guests.  The guests must have the\n  65:\t\tvirtio_transport.ko driver loaded to use the virtio-vsock device.\n  66:\t\n  67:\t\tTo compile this driver as a module, choose M here: the module will be called\n  68:\t\tvhost_vsock.\n  69:\t\n  70:\tconfig VHOST_VDPA\n  71:\t\ttristate \"Vhost driver for vDPA-based backend\"\n  72:\t\tdepends on EVENTFD\n  73:\t\tselect VHOST\n  74:\t\tselect IRQ_BYPASS_MANAGER\n  75:\t\tdepends on VDPA\n  76:\t\thelp\n  77:\t\t  This kernel module can be loaded in host kernel to accelerate\n  78:\t\t  guest virtio devices with the vDPA-based backends.\n  79:\t\n"
}

9/2 2026/10/29 04:41 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 37453
							output: 35
							thoughts: 112
						

Thoughts:
**Examining the vsock Configuration Landscape**

Okay, let's break this down. First, I need to understand which configurations are truly relevant here. I see `VSOCKETS`, `VSOCKETS_LOOPBACK`, `VIRTIO_VSOCKETS`, and `VHOST_VSOCK` jumping out. I already know that `VIRTIO_VSOCKETS_COMMON` is an automatic dependency of the last three, due to the logic.

Next, I need to get a concrete picture of how this is all implemented. I'll need to drill down into the source code to get the details. Specifically, I'll need to understand how `vsock_bpf.c` is compiled, and the details surrounding `net/vmw_vsock/Makefile`. I'm going to look to that file to start to unpack the dependencies and the build process.



10/2 2026/10/29 04:42 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/Makefile",
  "FirstLine": 1,
  "LineCount": 30
}

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"
}

11/2 2026/10/29 04:42 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 38239
							output: 44
							thoughts: 2202
						

Thoughts:
**Analyzing VSOCK Datagram Support Implementation for Fuzzing**

Alright, let's break this down. My goal is to identify key entry points and logic within this VSOCK datagram implementation, specifically for fuzzing purposes. First, the dependency chain is clear: `vsock_bpf.o` relies on `BPF_SYSCALL`, and different transports are involved like `VMWARE_VMCI_VSOCKETS` and `HYPERV_VSOCKETS`. I've noted the restrictions already in `hyperv_transport.c`, and in `vmci_transport.c`, and in `vsock_bpf.c`

Key configurations for this feature appear to be `VSOCKETS`, `VSOCKETS_LOOPBACK`, `VIRTIO_VSOCKETS`, and `VHOST_VSOCK`. I've confirmed that `VIRTIO_VSOCKETS` needs `VSOCKETS` and `VIRTIO`, while others have cascading dependencies.

Now, let's get to the meat of the patch. I've dissected the code changes to determine which parts are most relevant for targeted fuzzing. I'm focusing on non-ubiquitous, feature-specific functions that implement the *new* datagram logic, or the *altered* ones.

Here's my initial list of potentially interesting functions: `vhost_transport_do_send_pkt`, `vhost_transport_dgram_allow`, `vsock_assign_transport`, `vsock_dgram_get_transport`, `vsock_bind_common`, `virtio_vsock_rx_fill`, `virtio_transport_dgram_allow_check`, `virtio_transport_rx_work`, `virtio_transport_get_type`, `virtio_transport_dgram_skb_destructor`, `virtio_transport_send_pkt_info`, `virtio_transport_consume_skb_sent`, `virtio_transport_dgram_dequeue`, `virtio_transport_dgram_bind`, `virtio_transport_dgram_allow`, `virtio_transport_dgram_transport_match`, `virtio_transport_dgram_enqueue`, `virtio_transport_valid_type`, `virtio_transport_dgram_source_allow` and `virtio_transport_recv_pkt`, `vmci_transport_dgram_payload_len`.

However, the key question is whether these are suitable *symbols* for the fuzzer. I need functions that are *not* static, or, if static, *called* by a non-static exported symbol, such that I can use them as target points.

I've checked the symbol attributes and found the following are suitable:
*   `vsock_dgram_get_transport`: is non-static, `EXPORT_SYMBOL_GPL`.
*   `vsock_bind_common`: is non-static, `EXPORT_SYMBOL_GPL`.
*   `virtio_transport_dgram_enqueue`: non-static, `EXPORT_SYMBOL_GPL`.
*   `virtio_transport_dgram_dequeue`: non-static, `EXPORT_SYMBOL_GPL`.
*   `virtio_transport_recv_pkt`: non-static, `EXPORT_SYMBOL_GPL`.
*   `virtio_transport_dgram_transport_match`: is non-static, `EXPORT_SYMBOL_GPL`.

Functions like `vsock_dgram_sendmsg` and `vsock_dgram_connect`, are static, but if they are called by `EXPORT_SYMBOL`s, they can still be useful. I'll need to double check the call stack of `vsock_dgram_ops.sendmsg`. Static inline functions are useless, and if changes are primarily in static inline helpers, I will need to identify non-static, feature-specific caller functions that exercise them.

So, I'll need to go through the code again, confirm the call chains, and refine this list to ensure that the fuzzing efforts are properly targeted and effective.



12/2 2026/10/29 04:42 tool grepper
0m Args:
{
  "Expression": "virtio_transport_send_pkt_info",
  "PathPrefixes": [
    "net/vmw_vsock/",
    "include/"
  ]
}

Results:
{
  "Output": "net/vmw_vsock/virtio_transport_common.c=220=static struct sk_buff *virtio_transport_alloc_skb(struct virtio_vsock_pkt_info *info,\n--\nnet/vmw_vsock/virtio_transport_common.c-306- */\nnet/vmw_vsock/virtio_transport_common.c:307:static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,\nnet/vmw_vsock/virtio_transport_common.c-308-\t\t\t\t\t  struct virtio_vsock_pkt_info *info)\n--\nnet/vmw_vsock/virtio_transport_common.c=709=static int virtio_transport_send_credit_update(struct vsock_sock *vsk)\n--\nnet/vmw_vsock/virtio_transport_common.c-716-\nnet/vmw_vsock/virtio_transport_common.c:717:\treturn virtio_transport_send_pkt_info(vsk, \u0026info);\nnet/vmw_vsock/virtio_transport_common.c-718-}\n--\nnet/vmw_vsock/virtio_transport_common.c=1305=int virtio_transport_connect(struct vsock_sock *vsk)\n--\nnet/vmw_vsock/virtio_transport_common.c-1312-\nnet/vmw_vsock/virtio_transport_common.c:1313:\treturn virtio_transport_send_pkt_info(vsk, \u0026info);\nnet/vmw_vsock/virtio_transport_common.c-1314-}\n--\nnet/vmw_vsock/virtio_transport_common.c=1317=int virtio_transport_shutdown(struct vsock_sock *vsk, int mode)\n--\nnet/vmw_vsock/virtio_transport_common.c-1328-\nnet/vmw_vsock/virtio_transport_common.c:1329:\treturn virtio_transport_send_pkt_info(vsk, \u0026info);\nnet/vmw_vsock/virtio_transport_common.c-1330-}\n--\nnet/vmw_vsock/virtio_transport_common.c=1334=virtio_transport_dgram_enqueue(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-1350-\nnet/vmw_vsock/virtio_transport_common.c:1351:\treturn virtio_transport_send_pkt_info(vsk, \u0026info);\nnet/vmw_vsock/virtio_transport_common.c-1352-}\n--\nnet/vmw_vsock/virtio_transport_common.c=1356=virtio_transport_stream_enqueue(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-1367-\nnet/vmw_vsock/virtio_transport_common.c:1368:\treturn virtio_transport_send_pkt_info(vsk, \u0026info);\nnet/vmw_vsock/virtio_transport_common.c-1369-}\n--\nnet/vmw_vsock/virtio_transport_common.c=1396=static int virtio_transport_reset(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-1409-\nnet/vmw_vsock/virtio_transport_common.c:1410:\treturn virtio_transport_send_pkt_info(vsk, \u0026info);\nnet/vmw_vsock/virtio_transport_common.c-1411-}\n--\nnet/vmw_vsock/virtio_transport_common.c=1420=static int virtio_transport_reset_no_sock(const struct virtio_transport *t,\n--\nnet/vmw_vsock/virtio_transport_common.c-1436-\t\t * t-\u003esend_pkt(), instead of relying on\nnet/vmw_vsock/virtio_transport_common.c:1437:\t\t * virtio_transport_send_pkt_info() to pass it. It is not needed\nnet/vmw_vsock/virtio_transport_common.c-1438-\t\t * by virtio_transport_alloc_skb().\n--\nnet/vmw_vsock/virtio_transport_common.c=1763=virtio_transport_send_response(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-1775-\nnet/vmw_vsock/virtio_transport_common.c:1776:\treturn virtio_transport_send_pkt_info(vsk, \u0026info);\nnet/vmw_vsock/virtio_transport_common.c-1777-}\n"
}

13/2 2026/10/29 04:42 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 42642
							output: 304
							thoughts: 1314
						

Thoughts:
**Fuzzing Verdict: WorthFuzzing = True**

Okay, let's break this down. My initial instinct is to identify key focus symbols within this patch, and I've zeroed in on several functions that seem particularly relevant: `virtio_transport_dgram_enqueue`, `virtio_transport_dgram_dequeue`, `virtio_transport_dgram_bind`, `virtio_transport_recv_pkt`, `vsock_dgram_get_transport`, `vsock_bind_common`, `vsock_dgram_sendmsg`, and `vsock_dgram_connect`. These appear to be the core building blocks of this new datagram support.

Now, a quick check of `vsock_dgram_sendmsg` to determine if it's inlinable. Given its size, ~100 lines, the compiler won't likely inline it when used as a function pointer within `proto_ops`. The same should hold true for `vsock_dgram_connect`. This means we have well-defined entry points for fuzzing.

Next, I need to verify the necessary configurations. I'm noting `VSOCKETS`, `VSOCKETS_LOOPBACK`, `VIRTIO_VSOCKETS`, and `VHOST_VSOCK`. I'm also confirming that `VIRTIO_VSOCKETS_COMMON` is automatically selected with these. Fine, good to know. What about `VMWARE_VMCI_VSOCKETS`? Hmm, interesting that the patch touches `vmci_transport.c`, but that one is a secondary concern. The key configurations are `VSOCKETS`, `VSOCKETS_LOOPBACK`, `VIRTIO_VSOCKETS`, and `VHOST_VSOCK`.

Let's do a sanity check:

1.  Reachability is key. Since this patch targets AF\_VSOCK with virtio-vsock, vhost-vsock, and vsock\_loopback, which are routinely tested in QEMU/KVM environments, the code *is* reachable.

2.  The patch *does* alter reachable executable code, introducing SOCK_DGRAM support to these vsock transports, altering everything from packet routing and binding to enqueuing, dequeuing, buffer management, and reception. Absolutely fuzz-worthy.

Now, let's solidify the selection of focus symbols. I am deliberately avoiding ubiquitous functions: `vsock_dgram_sendmsg`, `vsock_dgram_connect`, `vsock_dgram_get_transport`, `vsock_bind_common`, `virtio_transport_dgram_enqueue`, `virtio_transport_dgram_dequeue`, `virtio_transport_recv_pkt`, `vhost_transport_do_send_pkt`, `virtio_transport_dgram_transport_match`, `virtio_transport_socket_init`, `virtio_vsock_rx_fill`. The ones I've highlighted stand out because they are *specific* and targeted:

*   `vsock_dgram_get_transport`: New transport selection logic.
*   `vsock_bind_common`: New shared binding function.
*   `virtio_transport_dgram_enqueue`: Virtio packet transmission.
*   `virtio_transport_dgram_dequeue`: Datagram receiving from socket receive queue.
*   `virtio_transport_recv_pkt`: Virtio-vsock packet reception, now handling `VIRTIO_VSOCK_TYPE_DGRAM`.
*   `vhost_transport_do_send_pkt`: Vhost-vsock modifications for datagram limits.
*   `vsock_dgram_sendmsg`: af_vsock dgram syscall handler with the new routing and enqueue logic.
*   `vsock_dgram_connect`: af_vsock dgram connect syscall handler with the new routing.

In summary, this patch introduces significant changes to core AF_VSOCK protocol code and the virtio/vhost drivers. The new datagram support is fully reachable in standard virtualized environments and demands fuzzing. Therefore, the result is `WorthFuzzing=true`.

Now, time to call the `set-results` tool.



14/2 2026/10/29 04:42 tool set-results
0m Args:
{
  "EnableConfigs": [
    "VSOCKETS",
    "VSOCKETS_LOOPBACK",
    "VIRTIO_VSOCKETS",
    "VHOST_VSOCK"
  ],
  "FocusSymbols": [
    "vsock_dgram_get_transport",
    "vsock_bind_common",
    "virtio_transport_dgram_enqueue",
    "virtio_transport_dgram_dequeue",
    "virtio_transport_recv_pkt",
    "vhost_transport_do_send_pkt",
    "vsock_dgram_sendmsg",
    "vsock_dgram_connect"
  ],
  "Reasoning": "The patch series adds SOCK_DGRAM datagram support to virtio-vsock, vhost-vsock, and vsock loopback transports. It introduces new datagram transport selection logic (vsock_dgram_get_transport), refactors port binding (vsock_bind_common), implements datagram queuing and dequeuing (virtio_transport_dgram_enqueue, virtio_transport_dgram_dequeue), handles datagram packet receipt (virtio_transport_recv_pkt), and modifies vhost descriptor processing (vhost_transport_do_send_pkt). AF_VSOCK and virtio/vhost devices are standard networking facilities accessible in virtualized environments, making these changes well worth fuzzing.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "VSOCKETS",
    "VSOCKETS_LOOPBACK",
    "VIRTIO_VSOCKETS",
    "VHOST_VSOCK"
  ],
  "FocusSymbols": [
    "vsock_dgram_get_transport",
    "vsock_bind_common",
    "virtio_transport_dgram_enqueue",
    "virtio_transport_dgram_dequeue",
    "virtio_transport_recv_pkt",
    "vhost_transport_do_send_pkt",
    "vsock_dgram_sendmsg",
    "vsock_dgram_connect"
  ],
  "Reasoning": "The patch series adds SOCK_DGRAM datagram support to virtio-vsock, vhost-vsock, and vsock loopback transports. It introduces new datagram transport selection logic (vsock_dgram_get_transport), refactors port binding (vsock_bind_common), implements datagram queuing and dequeuing (virtio_transport_dgram_enqueue, virtio_transport_dgram_dequeue), handles datagram packet receipt (virtio_transport_recv_pkt), and modifies vhost descriptor processing (vhost_transport_do_send_pkt). AF_VSOCK and virtio/vhost devices are standard networking facilities accessible in virtualized environments, making these changes well worth fuzzing.",
  "WorthFuzzing": true
}

15/1 2026/10/29 04:42 action If
8m Args:
{
  "WorthFuzzing": true
}

Results:
null

16/2 2026/10/29 04:42 agent kmsan-evaluator
8m Model:
core

Args:
null

Results:
{
  "KMSANReasoning": "The patch series implements SOCK_DGRAM support for virtio-vsock and vhost-vsock, adding datagram enqueue/dequeue, transport selection, loopback and peer routing, and ioctl/poll handling.\n\nKMSAN vs. KASAN/standard detectors evaluation:\n1. Potential bugs introduced or touched by this patch series primarily concern socket buffer lifecycle management (skb allocation, trimming, freeing via kfree_skb/sock_wfree), module and socket reference counting (try_module_get/module_put, sock_put), locking synchronization (vsock_register_mutex, vsock_table_lock, vvs-\u003etx_lock, socket locks), and bounds/truncation checks. These risks are covered by standard bug detectors, KASAN, and LOCKDEP.\n2. Information leaks / copy-to-user inspection:\n   - In `virtio_transport_dgram_dequeue()`, user addresses are constructed using `vsock_addr_init()`, which zeroes the entire `struct sockaddr_vm` (including structure padding) via `memset` prior to initializing field values.\n   - Datagram payloads copied to user space via `skb_copy_datagram_iter()` are bounded by `payload_len`, which is strictly validated against the received descriptor length `len` and trimmed with `pskb_trim_unique()`.\n   - The ioctl handler for SIOCINQ reports `n_bytes`, a cleanly initialized scalar integer.\n3. Uninitialized state inspection:\n   - `struct virtio_vsock_skb_cb` fields (`to_host`, `tx_accounted`) are stored in `skb-\u003ecb`, which is zeroed on skb allocation (`skbuff_clear()`), and are explicitly assigned before being queried in control flow or copied into socket structures.\n   - In `virtio_vsock_rx_fill()`, the scatterlist array `vsock-\u003ein_bufs` is initialized with `sg_init_table()` prior to descriptor submission.\n\nBecause all newly introduced structures, variables, and buffer copies are properly zeroed or initialized, and any memory-safety risks are detectable by KASAN and LOCKDEP, a dedicated KMSAN fuzzing 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 827c224662592a09246125151432c72c4a814d76
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Oct 29 04:41:17 2026 +0000

    syz-cluster: applied patch under review

diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
index 0cb87971d74919..2294ed8d30a84b 100644
--- a/drivers/vhost/vsock.c
+++ b/drivers/vhost/vsock.c
@@ -32,7 +32,8 @@
 static const int vhost_vsock_bits[] = {
 	VHOST_FEATURES,
 	VIRTIO_F_ACCESS_PLATFORM,
-	VIRTIO_VSOCK_F_SEQPACKET
+	VIRTIO_VSOCK_F_SEQPACKET,
+	VIRTIO_VSOCK_F_DGRAM,
 };
 
 #define VHOST_VSOCK_FEATURES VHOST_FEATURES_U64(vhost_vsock_bits, 0)
@@ -62,6 +63,7 @@ struct vhost_vsock {
 	u32 guest_cid;
 	bool seqpacket_allow;
 	bool ever_started; /* set on first SET_RUNNING(1); never cleared */
+	bool dgram_allow;
 };
 
 static u32 vhost_transport_get_local_cid(void)
@@ -183,6 +185,14 @@ vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
 		 * buffer, we split it using multiple buffers.
 		 */
 		if (payload_len > iov_len - sizeof(*hdr)) {
+			/* Datagrams must fit in a single descriptor chain. */
+			if (le16_to_cpu(hdr->type) == VIRTIO_VSOCK_TYPE_DGRAM) {
+				kfree_skb(skb);
+				vhost_add_used(vq, head, 0);
+				added = true;
+				continue;
+			}
+
 			payload_len = iov_len - sizeof(*hdr);
 
 			/* As we are copying pieces of large packet's buffer to
@@ -436,6 +446,8 @@ static bool vhost_transport_msgzerocopy_allow(void)
 
 static bool vhost_transport_seqpacket_allow(struct vsock_sock *vsk,
 					    u32 remote_cid);
+static bool vhost_transport_dgram_allow(struct vsock_sock *vsk,
+					u32 cid, u32 port);
 
 static bool
 vhost_transport_stream_allow(struct vsock_sock *vsk, u32 cid, u32 port)
@@ -460,7 +472,8 @@ static struct virtio_transport vhost_transport = {
 		.dgram_enqueue            = virtio_transport_dgram_enqueue,
 		.dgram_dequeue            = virtio_transport_dgram_dequeue,
 		.dgram_bind               = virtio_transport_dgram_bind,
-		.dgram_allow              = virtio_transport_dgram_allow,
+		.dgram_allow              = vhost_transport_dgram_allow,
+		.dgram_transport_match    = virtio_transport_dgram_transport_match,
 
 		.stream_enqueue           = virtio_transport_stream_enqueue,
 		.stream_dequeue           = virtio_transport_stream_dequeue,
@@ -516,6 +529,24 @@ static bool vhost_transport_seqpacket_allow(struct vsock_sock *vsk,
 	return seqpacket_allow;
 }
 
+static bool vhost_transport_dgram_allow(struct vsock_sock *vsk,
+					u32 cid, u32 port)
+{
+	struct net *net = sock_net(sk_vsock(vsk));
+	struct vhost_vsock *vsock;
+	bool dgram_allow = false;
+
+	rcu_read_lock();
+	vsock = vhost_vsock_get(cid, net);
+
+	if (vsock)
+		dgram_allow = vsock->dgram_allow;
+
+	rcu_read_unlock();
+
+	return dgram_allow;
+}
+
 static void vhost_vsock_handle_tx_kick(struct vhost_work *work)
 {
 	struct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue,
@@ -737,6 +768,7 @@ static int vhost_vsock_dev_open(struct inode *inode, struct file *file)
 	vsock->guest_cid = 0; /* no CID assigned yet */
 	vsock->seqpacket_allow = false;
 	vsock->ever_started = false;
+	vsock->dgram_allow = false;
 
 	atomic_set(&vsock->queued_replies, 0);
 
@@ -890,6 +922,7 @@ static int vhost_vsock_set_features(struct vhost_vsock *vsock, u64 features)
 	}
 
 	vsock->seqpacket_allow = features & (1ULL << VIRTIO_VSOCK_F_SEQPACKET);
+	vsock->dgram_allow = features & (1ULL << VIRTIO_VSOCK_F_DGRAM);
 
 	for (i = 0; i < ARRAY_SIZE(vsock->vqs); i++) {
 		vq = &vsock->vqs[i];
diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h
index f91704731057ed..91945031fcc535 100644
--- a/include/linux/virtio_vsock.h
+++ b/include/linux/virtio_vsock.h
@@ -12,6 +12,9 @@
 struct virtio_vsock_skb_cb {
 	bool reply;
 	bool tap_delivered;
+	/* Datagram payload is still charged to bytes_unsent. */
+	bool tx_accounted;
+	bool to_host;
 	u32 offset;
 };
 
@@ -171,6 +174,7 @@ struct virtio_vsock_sock {
 
 struct virtio_vsock_pkt_info {
 	u32 remote_cid, remote_port;
+	const struct virtio_transport *t_ops;
 	struct vsock_sock *vsk;
 	struct msghdr *msg;
 	struct net *net;
@@ -261,6 +265,7 @@ bool virtio_transport_stream_allow(struct vsock_sock *vsk, u32 cid, u32 port);
 int virtio_transport_dgram_bind(struct vsock_sock *vsk,
 				struct sockaddr_vm *addr);
 bool virtio_transport_dgram_allow(struct vsock_sock *vsk, u32 cid, u32 port);
+bool virtio_transport_dgram_transport_match(const struct vsock_transport *t);
 
 int virtio_transport_connect(struct vsock_sock *vsk);
 
@@ -274,6 +279,7 @@ virtio_transport_stream_enqueue(struct vsock_sock *vsk,
 				size_t len);
 int
 virtio_transport_dgram_enqueue(struct vsock_sock *vsk,
+			       const struct vsock_transport *transport,
 			       struct sockaddr_vm *remote_addr,
 			       struct msghdr *msg,
 			       size_t len);
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index e8481f12312e91..f9bcd703a7768a 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -121,9 +121,15 @@ struct vsock_transport {
 	int (*dgram_bind)(struct vsock_sock *, struct sockaddr_vm *);
 	int (*dgram_dequeue)(struct vsock_sock *vsk, struct msghdr *msg,
 			     size_t len, int flags);
-	int (*dgram_enqueue)(struct vsock_sock *, struct sockaddr_vm *,
-			     struct msghdr *, size_t len);
+	int (*dgram_enqueue)(struct vsock_sock *vsk,
+			     const struct vsock_transport *transport,
+			     struct sockaddr_vm *remote_addr,
+			     struct msghdr *msg, size_t len);
 	bool (*dgram_allow)(struct vsock_sock *vsk, u32 cid, u32 port);
+	/* Optional: another transport can use this socket's datagram state. */
+	bool (*dgram_transport_match)(const struct vsock_transport *transport);
+	/* Optional payload length override, called with the receive queue locked. */
+	size_t (*dgram_payload_len)(const struct sk_buff *skb);
 
 	/* STREAM. */
 	/* TODO: stream_bind() */
@@ -220,6 +226,7 @@ void vsock_remove_pending(struct sock *listener, struct sock *pending);
 void vsock_enqueue_accept(struct sock *listener, struct sock *connected);
 void vsock_pending_to_accept(struct sock *listener, struct sock *pending);
 void vsock_insert_connected(struct vsock_sock *vsk);
+int vsock_bind_common(struct vsock_sock *vsk, struct sockaddr_vm *addr);
 void vsock_remove_bound(struct vsock_sock *vsk);
 void vsock_remove_connected(struct vsock_sock *vsk);
 struct sock *vsock_find_bound_socket(struct sockaddr_vm *addr);
@@ -237,6 +244,9 @@ void vsock_remove_sock(struct vsock_sock *vsk);
 void vsock_for_each_connected_socket(struct vsock_transport *transport,
 				     void (*fn)(struct sock *sk));
 int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk);
+const struct vsock_transport *
+vsock_dgram_get_transport(struct vsock_sock *vsk,
+			  struct sockaddr_vm *remote_addr);
 bool vsock_find_cid(unsigned int cid);
 void vsock_linger(struct sock *sk);
 
diff --git a/include/uapi/linux/virtio_vsock.h b/include/uapi/linux/virtio_vsock.h
index 64738838bee573..27b4b2b8bf13b9 100644
--- a/include/uapi/linux/virtio_vsock.h
+++ b/include/uapi/linux/virtio_vsock.h
@@ -40,6 +40,7 @@
 
 /* The feature bitmap for virtio vsock */
 #define VIRTIO_VSOCK_F_SEQPACKET	1	/* SOCK_SEQPACKET supported */
+#define VIRTIO_VSOCK_F_DGRAM		3	/* SOCK_DGRAM supported */
 
 struct virtio_vsock_config {
 	__le64 guest_cid;
@@ -69,6 +70,7 @@ struct virtio_vsock_hdr {
 enum virtio_vsock_type {
 	VIRTIO_VSOCK_TYPE_STREAM = 1,
 	VIRTIO_VSOCK_TYPE_SEQPACKET = 2,
+	VIRTIO_VSOCK_TYPE_DGRAM = 3,
 };
 
 enum virtio_vsock_op {
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index d0508887a2fbac..6419498a6fe276 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -136,6 +136,7 @@
 #include <linux/types.h>
 #include <linux/bitops.h>
 #include <linux/cred.h>
+#include <linux/err.h>
 #include <linux/errqueue.h>
 #include <linux/init.h>
 #include <linux/io.h>
@@ -599,6 +600,7 @@ static void vsock_deassign_transport(struct vsock_sock *vsk)
 int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
 {
 	const struct vsock_transport *new_transport;
+	const struct vsock_transport *fallback_transport = NULL;
 	struct sock *sk = sk_vsock(vsk);
 	unsigned int remote_cid = vsk->remote_addr.svm_cid;
 	__u8 remote_flags;
@@ -622,7 +624,12 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
 
 	switch (sk->sk_type) {
 	case SOCK_DGRAM:
-		new_transport = transport_dgram;
+		if (transport_dgram)
+			new_transport = transport_dgram;
+		else if (transport_g2h)
+			new_transport = transport_g2h;
+		else
+			new_transport = transport_h2g;
 		break;
 	case SOCK_STREAM:
 	case SOCK_SEQPACKET:
@@ -661,6 +668,11 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
 		ret = -ENODEV;
 		goto err;
 	}
+	if (sk->sk_type == SOCK_DGRAM && !transport_dgram &&
+	    new_transport == transport_g2h && transport_h2g &&
+	    transport_h2g != new_transport &&
+	    try_module_get(transport_h2g->module))
+		fallback_transport = transport_h2g;
 
 	/* It's safe to release the mutex after a successful try_module_get().
 	 * Whichever transport `new_transport` points at, it won't go away until
@@ -697,6 +709,14 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
 	}
 
 	ret = new_transport->init(vsk, psk);
+	if (ret == -ESOCKTNOSUPPORT && fallback_transport) {
+		module_put(new_transport->module);
+		new_transport = fallback_transport;
+		fallback_transport = NULL;
+		ret = new_transport->init(vsk, psk);
+	}
+	if (fallback_transport)
+		module_put(fallback_transport->module);
 	if (ret) {
 		module_put(new_transport->module);
 		return ret;
@@ -711,6 +731,98 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
 }
 EXPORT_SYMBOL_GPL(vsock_assign_transport);
 
+/**
+ * vsock_dgram_get_transport - select a transport for a datagram destination
+ * @vsk: socket whose datagram state will be used
+ * @remote_addr: destination address
+ *
+ * The caller must hold the socket lock and release the returned transport's
+ * module reference after use. Dedicated datagram transports retain their
+ * original selection; compatible transports share the socket's existing state.
+ * Parent fallback sets VMADDR_FLAG_TO_HOST to preserve that route on connect.
+ *
+ * Return: a referenced transport or an ERR_PTR on failure.
+ */
+const struct vsock_transport *
+vsock_dgram_get_transport(struct vsock_sock *vsk,
+			  struct sockaddr_vm *remote_addr)
+{
+	const struct vsock_transport *owner = vsk->transport;
+	const struct vsock_transport *transport;
+	struct sock *sk = sk_vsock(vsk);
+	u32 cid = remote_addr->svm_cid;
+	u32 local_cid;
+	bool to_host = false;
+	int err = -ENODEV;
+
+	if (!owner)
+		return ERR_PTR(-ENODEV);
+
+	mutex_lock(&vsock_register_mutex);
+	if (!owner->dgram_transport_match) {
+		transport = owner;
+	} else if (vsock_use_local_transport(cid)) {
+		transport = transport_local;
+	} else if (cid <= VMADDR_CID_HOST ||
+		   (remote_addr->svm_flags & VMADDR_FLAG_TO_HOST)) {
+		transport = transport_g2h;
+	} else if (transport_h2g &&
+		   (!transport_h2g->has_remote_cid ||
+		    transport_h2g->has_remote_cid(vsk, cid))) {
+		transport = transport_h2g;
+	} else if (sock_net(sk)->vsock.g2h_fallback && transport_g2h &&
+		   transport_g2h->has_remote_cid &&
+		   transport_g2h->has_remote_cid(vsk, cid)) {
+		transport = transport_g2h;
+		to_host = true;
+	} else {
+		transport = transport_h2g;
+	}
+
+	if (!transport)
+		goto out;
+	if (owner->dgram_transport_match &&
+	    !owner->dgram_transport_match(transport)) {
+		err = -ESOCKTNOSUPPORT;
+		goto out;
+	}
+	/* Loopback source addresses must route replies back to loopback. */
+	if (owner->dgram_transport_match && transport == transport_local &&
+	    vsk->local_addr.svm_cid != VMADDR_CID_ANY &&
+	    !vsock_use_local_transport(vsk->local_addr.svm_cid)) {
+		err = -EADDRNOTAVAIL;
+		goto out;
+	}
+	if (!try_module_get(transport->module))
+		goto out;
+	mutex_unlock(&vsock_register_mutex);
+
+	if (owner->dgram_transport_match) {
+		local_cid = transport->get_local_cid();
+		if (vsk->local_addr.svm_cid != VMADDR_CID_ANY &&
+		    local_cid != VMADDR_CID_LOCAL &&
+		    vsk->local_addr.svm_cid != local_cid) {
+			err = -EADDRNOTAVAIL;
+			goto put_transport;
+		}
+	}
+	if (!transport->dgram_allow(vsk, cid, remote_addr->svm_port)) {
+		err = -EINVAL;
+		goto put_transport;
+	}
+	if (to_host)
+		remote_addr->svm_flags |= VMADDR_FLAG_TO_HOST;
+	return transport;
+
+put_transport:
+	module_put(transport->module);
+	return ERR_PTR(err);
+out:
+	mutex_unlock(&vsock_register_mutex);
+	return ERR_PTR(err);
+}
+EXPORT_SYMBOL_GPL(vsock_dgram_get_transport);
+
 /*
  * Provide safe access to static transport_{h2g,g2h,dgram,local} callbacks.
  * Otherwise we may race with module removal. Do not use on `vsk->transport`.
@@ -832,8 +944,8 @@ static void vsock_pending_work(struct work_struct *work)
 
 /**** SOCKET OPERATIONS ****/
 
-static int __vsock_bind_connectible(struct vsock_sock *vsk,
-				    struct sockaddr_vm *addr)
+static int __vsock_bind_common(struct vsock_sock *vsk,
+			       struct sockaddr_vm *addr)
 {
 	struct net *net = sock_net(sk_vsock(vsk));
 	struct sockaddr_vm new_addr;
@@ -888,11 +1000,26 @@ static int __vsock_bind_connectible(struct vsock_sock *vsk,
 	return 0;
 }
 
-static int __vsock_bind_dgram(struct vsock_sock *vsk,
-			      struct sockaddr_vm *addr)
+/**
+ * vsock_bind_common - bind a socket using the core port allocator
+ * @vsk: socket to bind
+ * @addr: local address to bind to
+ *
+ * The caller must hold the socket lock.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int vsock_bind_common(struct vsock_sock *vsk, struct sockaddr_vm *addr)
 {
-	return vsk->transport->dgram_bind(vsk, addr);
+	int ret;
+
+	spin_lock_bh(&vsock_table_lock);
+	ret = __vsock_bind_common(vsk, addr);
+	spin_unlock_bh(&vsock_table_lock);
+
+	return ret;
 }
+EXPORT_SYMBOL_GPL(vsock_bind_common);
 
 static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr)
 {
@@ -914,13 +1041,11 @@ static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr)
 	switch (sk->sk_socket->type) {
 	case SOCK_STREAM:
 	case SOCK_SEQPACKET:
-		spin_lock_bh(&vsock_table_lock);
-		retval = __vsock_bind_connectible(vsk, addr);
-		spin_unlock_bh(&vsock_table_lock);
+		retval = vsock_bind_common(vsk, addr);
 		break;
 
 	case SOCK_DGRAM:
-		retval = __vsock_bind_dgram(vsk, addr);
+		retval = vsk->transport->dgram_bind(vsk, addr);
 		break;
 
 	default:
@@ -1027,7 +1152,7 @@ static void __vsock_release(struct sock *sk, int level)
 
 	if (vsk->transport)
 		vsk->transport->release(vsk);
-	else if (sock_type_connectible(sk->sk_type))
+	else
 		vsock_remove_sock(vsk);
 
 	sock_orphan(sk);
@@ -1331,16 +1456,20 @@ static __poll_t vsock_poll(struct file *file, struct socket *sock,
 		mask |= vsock_poll_shutdown(sk, peer_shutdown);
 
 		/* For datagram sockets we can read if there is something in
-		 * the queue and write as long as the socket isn't shutdown for
-		 * sending.
+		 * the queue and write if the send buffer has space and the
+		 * socket isn't shutdown for sending.
 		 */
 		if (!skb_queue_empty_lockless(&sk->sk_receive_queue) ||
 		    (sk->sk_shutdown & RCV_SHUTDOWN)) {
 			mask |= EPOLLIN | EPOLLRDNORM;
 		}
 
-		if (!(sk->sk_shutdown & SEND_SHUTDOWN))
-			mask |= EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND;
+		if (!(sk->sk_shutdown & SEND_SHUTDOWN)) {
+			if (sock_writeable(sk))
+				mask |= EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND;
+			else
+				sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);
+		}
 
 	} else if (sock_type_connectible(sk->sk_type)) {
 		const struct vsock_transport *transport;
@@ -1466,7 +1595,8 @@ static int vsock_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
 		 */
 
 		if (remote_addr->svm_cid == VMADDR_CID_ANY)
-			remote_addr->svm_cid = transport->get_local_cid();
+			remote_addr->svm_cid = transport->dgram_transport_match ?
+				VMADDR_CID_LOCAL : transport->get_local_cid();
 
 		if (!vsock_addr_bound(remote_addr)) {
 			err = -EINVAL;
@@ -1476,7 +1606,8 @@ static int vsock_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
 		remote_addr = &vsk->remote_addr;
 
 		if (remote_addr->svm_cid == VMADDR_CID_ANY)
-			remote_addr->svm_cid = transport->get_local_cid();
+			remote_addr->svm_cid = transport->dgram_transport_match ?
+				VMADDR_CID_LOCAL : transport->get_local_cid();
 
 		/* XXX Should connect() or this function ensure remote_addr is
 		 * bound?
@@ -1490,13 +1621,14 @@ static int vsock_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
 		goto out;
 	}
 
-	if (!transport->dgram_allow(vsk, remote_addr->svm_cid,
-				    remote_addr->svm_port)) {
-		err = -EINVAL;
+	transport = vsock_dgram_get_transport(vsk, remote_addr);
+	if (IS_ERR(transport)) {
+		err = PTR_ERR(transport);
 		goto out;
 	}
 
-	err = transport->dgram_enqueue(vsk, remote_addr, msg, len);
+	err = transport->dgram_enqueue(vsk, transport, remote_addr, msg, len);
+	module_put(transport->module);
 
 out:
 	release_sock(sk);
@@ -1510,6 +1642,7 @@ static int vsock_dgram_connect(struct socket *sock,
 	struct sock *sk;
 	struct vsock_sock *vsk;
 	struct sockaddr_vm *remote_addr;
+	const struct vsock_transport *transport;
 
 	sk = sock->sk;
 	vsk = vsock_sk(sk);
@@ -1531,11 +1664,16 @@ static int vsock_dgram_connect(struct socket *sock,
 	if (err)
 		goto out;
 
-	if (!vsk->transport->dgram_allow(vsk, remote_addr->svm_cid,
-					 remote_addr->svm_port)) {
-		err = -EINVAL;
+	if (remote_addr->svm_cid == VMADDR_CID_ANY &&
+	    vsk->transport->dgram_transport_match)
+		remote_addr->svm_cid = VMADDR_CID_LOCAL;
+
+	transport = vsock_dgram_get_transport(vsk, remote_addr);
+	if (IS_ERR(transport)) {
+		err = PTR_ERR(transport);
 		goto out;
 	}
+	module_put(transport->module);
 
 	memcpy(&vsk->remote_addr, remote_addr, sizeof(vsk->remote_addr));
 	sock->state = SS_CONNECTED;
@@ -1604,10 +1742,22 @@ static int vsock_do_ioctl(struct socket *sock, unsigned int cmd,
 			break;
 		}
 
-		n_bytes = vsock_stream_has_data(vsk);
-		if (n_bytes < 0) {
-			ret = n_bytes;
-			break;
+		if (sk->sk_type == SOCK_DGRAM) {
+			struct sk_buff *skb;
+
+			spin_lock_bh(&sk->sk_receive_queue.lock);
+			skb = skb_peek(&sk->sk_receive_queue);
+			if (skb && vsk->transport->dgram_payload_len)
+				n_bytes = vsk->transport->dgram_payload_len(skb);
+			else
+				n_bytes = skb ? skb->len : 0;
+			spin_unlock_bh(&sk->sk_receive_queue.lock);
+		} else {
+			n_bytes = vsock_stream_has_data(vsk);
+			if (n_bytes < 0) {
+				ret = n_bytes;
+				break;
+			}
 		}
 		ret = put_user(n_bytes, arg);
 		break;
diff --git a/net/vmw_vsock/hyperv_transport.c b/net/vmw_vsock/hyperv_transport.c
index 855b15971c359f..6a276ef6964ba1 100644
--- a/net/vmw_vsock/hyperv_transport.c
+++ b/net/vmw_vsock/hyperv_transport.c
@@ -443,6 +443,9 @@ static int hvs_sock_init(struct vsock_sock *vsk, struct vsock_sock *psk)
 	struct hvsock *hvs;
 	struct sock *sk = sk_vsock(vsk);
 
+	if (sk->sk_type == SOCK_DGRAM)
+		return -ESOCKTNOSUPPORT;
+
 	hvs = kzalloc_obj(*hvs);
 	if (!hvs)
 		return -ENOMEM;
@@ -563,6 +566,7 @@ static int hvs_dgram_dequeue(struct vsock_sock *vsk, struct msghdr *msg,
 }
 
 static int hvs_dgram_enqueue(struct vsock_sock *vsk,
+			     const struct vsock_transport *transport,
 			     struct sockaddr_vm *remote, struct msghdr *msg,
 			     size_t dgram_len)
 {
diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
index 4f9aa9c4c3aa59..d256274088b38c 100644
--- a/net/vmw_vsock/virtio_transport.c
+++ b/net/vmw_vsock/virtio_transport.c
@@ -54,9 +54,11 @@ struct virtio_vsock {
 	bool rx_run;
 	int rx_buf_nr;
 	int rx_buf_max_nr;
+	struct scatterlist in_bufs[MAX_SKB_FRAGS + 1];
 
 	u32 guest_cid;
 	bool seqpacket_allow;
+	bool dgram_allow;
 
 	/* These fields are used only in tx path in function
 	 * 'virtio_transport_send_pkt_work()', so to save
@@ -311,22 +313,31 @@ virtio_transport_cancel_pkt(struct vsock_sock *vsk)
 static void virtio_vsock_rx_fill(struct virtio_vsock *vsock)
 {
 	int total_len = VIRTIO_VSOCK_DEFAULT_RX_BUF_SIZE;
-	struct scatterlist pkt, *p;
 	struct virtqueue *vq;
 	struct sk_buff *skb;
 	int ret;
 
 	vq = vsock->vqs[VSOCK_VQ_RX];
+	/* A datagram must fit in one RX buffer, including its header. */
+	if (vsock->dgram_allow)
+		total_len = VIRTIO_VSOCK_SKB_HEADROOM +
+			    VIRTIO_VSOCK_MAX_PKT_BUF_SIZE;
 
 	do {
-		skb = virtio_vsock_alloc_linear_skb(total_len, GFP_KERNEL);
+		skb = virtio_vsock_alloc_skb(total_len, GFP_KERNEL);
 		if (!skb)
 			break;
 
 		memset(skb->head, 0, VIRTIO_VSOCK_SKB_HEADROOM);
-		sg_init_one(&pkt, virtio_vsock_hdr(skb), total_len);
-		p = &pkt;
-		ret = virtqueue_add_sgs(vq, &p, 0, 1, skb, GFP_KERNEL);
+		/* Post the header and the entire payload as one RX buffer. */
+		virtio_vsock_skb_put(skb, total_len - VIRTIO_VSOCK_SKB_HEADROOM);
+		skb_push(skb, VIRTIO_VSOCK_SKB_HEADROOM);
+		sg_init_table(vsock->in_bufs, ARRAY_SIZE(vsock->in_bufs));
+		ret = skb_to_sgvec(skb, vsock->in_bufs, 0, total_len);
+		skb_pull(skb, VIRTIO_VSOCK_SKB_HEADROOM);
+		if (ret >= 0)
+			ret = virtqueue_add_inbuf(vq, vsock->in_bufs, ret, skb,
+						  GFP_KERNEL);
 		if (ret < 0) {
 			kfree_skb(skb);
 			break;
@@ -547,6 +558,18 @@ bool virtio_transport_stream_allow(struct vsock_sock *vsk, u32 cid, u32 port)
 
 static bool virtio_transport_seqpacket_allow(struct vsock_sock *vsk,
 					     u32 remote_cid);
+static bool virtio_transport_dgram_allow_check(struct vsock_sock *vsk,
+					       u32 cid, u32 port);
+
+static int virtio_transport_socket_init(struct vsock_sock *vsk,
+					struct vsock_sock *psk)
+{
+	if (sk_vsock(vsk)->sk_type == SOCK_DGRAM &&
+	    !virtio_transport_dgram_allow_check(vsk, 0, 0))
+		return -ESOCKTNOSUPPORT;
+
+	return virtio_transport_do_socket_init(vsk, psk);
+}
 
 static bool virtio_transport_has_remote_cid(struct vsock_sock *vsk, u32 cid)
 {
@@ -561,7 +584,7 @@ static struct virtio_transport virtio_transport = {
 		.get_local_cid            = virtio_transport_get_local_cid,
 		.has_remote_cid           = virtio_transport_has_remote_cid,
 
-		.init                     = virtio_transport_do_socket_init,
+		.init                     = virtio_transport_socket_init,
 		.destruct                 = virtio_transport_destruct,
 		.release                  = virtio_transport_release,
 		.connect                  = virtio_transport_connect,
@@ -571,7 +594,8 @@ static struct virtio_transport virtio_transport = {
 		.dgram_bind               = virtio_transport_dgram_bind,
 		.dgram_dequeue            = virtio_transport_dgram_dequeue,
 		.dgram_enqueue            = virtio_transport_dgram_enqueue,
-		.dgram_allow              = virtio_transport_dgram_allow,
+		.dgram_allow              = virtio_transport_dgram_allow_check,
+		.dgram_transport_match    = virtio_transport_dgram_transport_match,
 
 		.stream_dequeue           = virtio_transport_stream_dequeue,
 		.stream_enqueue           = virtio_transport_stream_enqueue,
@@ -629,6 +653,25 @@ virtio_transport_seqpacket_allow(struct vsock_sock *vsk, u32 remote_cid)
 	return seqpacket_allow;
 }
 
+static bool
+virtio_transport_dgram_allow_check(struct vsock_sock *vsk, u32 cid, u32 port)
+{
+	struct virtio_vsock *vsock;
+	bool dgram_allow;
+
+	if (!vsock_net_mode_global(vsk))
+		return false;
+
+	dgram_allow = false;
+	rcu_read_lock();
+	vsock = rcu_dereference(the_virtio_vsock);
+	if (vsock)
+		dgram_allow = vsock->dgram_allow;
+	rcu_read_unlock();
+
+	return dgram_allow;
+}
+
 static void virtio_transport_rx_work(struct work_struct *work)
 {
 	struct virtio_vsock *vsock =
@@ -665,7 +708,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))) {
+				     len > sizeof(*hdr) + skb->len)) {
 				kfree_skb(skb);
 				continue;
 			}
@@ -677,8 +720,7 @@ static void virtio_transport_rx_work(struct work_struct *work)
 				continue;
 			}
 
-			if (payload_len)
-				virtio_vsock_skb_put(skb, payload_len);
+			pskb_trim_unique(skb, payload_len);
 
 			virtio_transport_deliver_tap_pkt(skb);
 
@@ -690,7 +732,8 @@ static void virtio_transport_rx_work(struct work_struct *work)
 	} while (!virtqueue_enable_cb(vq));
 
 out:
-	if (vsock->rx_buf_nr < vsock->rx_buf_max_nr / 2)
+	if (!vsock->rx_buf_nr ||
+	    vsock->rx_buf_nr < vsock->rx_buf_max_nr / 2)
 		virtio_vsock_rx_fill(vsock);
 out_nofill:
 	mutex_unlock(&vsock->rx_lock);
@@ -835,6 +878,9 @@ static int virtio_vsock_probe(struct virtio_device *vdev)
 	if (virtio_has_feature(vdev, VIRTIO_VSOCK_F_SEQPACKET))
 		vsock->seqpacket_allow = true;
 
+	if (virtio_has_feature(vdev, VIRTIO_VSOCK_F_DGRAM))
+		vsock->dgram_allow = true;
+
 	vdev->priv = vsock;
 
 	ret = virtio_vsock_vqs_init(vsock);
@@ -932,7 +978,8 @@ static struct virtio_device_id id_table[] = {
 };
 
 static unsigned int features[] = {
-	VIRTIO_VSOCK_F_SEQPACKET
+	VIRTIO_VSOCK_F_SEQPACKET,
+	VIRTIO_VSOCK_F_DGRAM,
 };
 
 static struct virtio_driver virtio_vsock_driver = {
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index 1e762a480df490..7c1ec8feb853c9 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -204,10 +204,18 @@ static u16 virtio_transport_get_type(struct sock *sk)
 {
 	if (sk->sk_type == SOCK_STREAM)
 		return VIRTIO_VSOCK_TYPE_STREAM;
+	else if (sk->sk_type == SOCK_DGRAM)
+		return VIRTIO_VSOCK_TYPE_DGRAM;
 	else
 		return VIRTIO_VSOCK_TYPE_SEQPACKET;
 }
 
+static void virtio_transport_dgram_skb_destructor(struct sk_buff *skb)
+{
+	virtio_transport_consume_skb_sent(skb, false);
+	sock_wfree(skb);
+}
+
 /* Returns new sk_buff on success, otherwise returns NULL. */
 static struct sk_buff *virtio_transport_alloc_skb(struct virtio_vsock_pkt_info *info,
 						  size_t payload_len,
@@ -303,6 +311,7 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
 	u32 src_cid, src_port, dst_cid, dst_port;
 	const struct virtio_transport *t_ops;
 	struct iov_iter_state msg_iter_state;
+	struct sock *sk = sk_vsock(vsk);
 	struct virtio_vsock_sock *vvs;
 	struct ubuf_info *uarg = NULL;
 	u32 pkt_len = info->pkt_len;
@@ -313,13 +322,18 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
 
 	info->type = virtio_transport_get_type(sk_vsock(vsk));
 
-	t_ops = virtio_transport_get_ops(vsk);
+	t_ops = info->t_ops ?: virtio_transport_get_ops(vsk);
 	if (unlikely(!t_ops))
 		return -EFAULT;
 
 	src_cid = t_ops->transport.get_local_cid();
+	/* Preserve explicit datagram bindings so loopback replies can find them. */
+	if (info->type == VIRTIO_VSOCK_TYPE_DGRAM &&
+	    src_cid == VMADDR_CID_LOCAL &&
+	    vsk->local_addr.svm_cid != VMADDR_CID_ANY)
+		src_cid = vsk->local_addr.svm_cid;
 	src_port = vsk->local_addr.svm_port;
-	if (!info->remote_cid) {
+	if (info->type != VIRTIO_VSOCK_TYPE_DGRAM && !info->remote_cid) {
 		dst_cid	= vsk->remote_addr.svm_cid;
 		dst_port = vsk->remote_addr.svm_port;
 	} else {
@@ -329,12 +343,24 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
 
 	vvs = vsk->trans;
 
-	/* virtio_transport_get_credit might return less than pkt_len credit */
-	pkt_len = virtio_transport_get_credit(vvs, pkt_len);
+	if (info->type == VIRTIO_VSOCK_TYPE_DGRAM) {
+		if (pkt_len > VIRTIO_VSOCK_MAX_PKT_BUF_SIZE)
+			return -EMSGSIZE;
+
+		/* Datagram sends are serialized by the socket lock. */
+		if (sk_wmem_alloc_get(sk) >= READ_ONCE(sk->sk_sndbuf)) {
+			sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);
+			set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
+			return -EAGAIN;
+		}
+	} else {
+		/* virtio_transport_get_credit might return less than pkt_len credit */
+		pkt_len = virtio_transport_get_credit(vvs, pkt_len);
 
-	/* Do not send zero length OP_RW pkt */
-	if (pkt_len == 0 && info->op == VIRTIO_VSOCK_OP_RW)
-		return pkt_len;
+		/* Do not send zero length OP_RW pkt */
+		if (pkt_len == 0 && info->op == VIRTIO_VSOCK_OP_RW)
+			return pkt_len;
+	}
 
 	if (info->msg && (info->msg->msg_flags & MSG_ZEROCOPY)) {
 		/* If 'info->msg' is not NULL, this is only VIRTIO_VSOCK_OP_RW.
@@ -348,7 +374,8 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
 			uarg = msg_zerocopy_realloc(sk_vsock(vsk), pkt_len,
 						    NULL, false);
 			if (!uarg) {
-				virtio_transport_put_credit(vvs, pkt_len);
+				if (info->type != VIRTIO_VSOCK_TYPE_DGRAM)
+					virtio_transport_put_credit(vvs, pkt_len);
 				return -ENOMEM;
 			}
 
@@ -393,7 +420,15 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
 			break;
 		}
 
-		virtio_transport_inc_tx_pkt(vvs, skb);
+		if (info->type == VIRTIO_VSOCK_TYPE_DGRAM) {
+			spin_lock_bh(&vvs->tx_lock);
+			vvs->bytes_unsent += skb_len;
+			spin_unlock_bh(&vvs->tx_lock);
+			VIRTIO_VSOCK_SKB_CB(skb)->tx_accounted = true;
+			skb->destructor = virtio_transport_dgram_skb_destructor;
+		} else {
+			virtio_transport_inc_tx_pkt(vvs, skb);
+		}
 
 		ret = t_ops->send_pkt(skb, info->net);
 		if (ret < 0)
@@ -416,7 +451,8 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
 	if (info->msg && ret < 0)
 		iov_iter_restore(&info->msg->msg_iter, &msg_iter_state);
 
-	virtio_transport_put_credit(vvs, rest_len);
+	if (info->type != VIRTIO_VSOCK_TYPE_DGRAM)
+		virtio_transport_put_credit(vvs, rest_len);
 
 	/* msg_zerocopy_realloc() initializes the ubuf_info refcnt to 1.
 	 * skb_zcopy_set() increases it for each skb, so we can drop that
@@ -620,7 +656,9 @@ void virtio_transport_consume_skb_sent(struct sk_buff *skb, bool consume)
 {
 	struct sock *s = skb->sk;
 
-	if (s && skb->len) {
+	if (s && skb->len &&
+	    (s->sk_type != SOCK_DGRAM ||
+	     VIRTIO_VSOCK_SKB_CB(skb)->tx_accounted)) {
 		struct vsock_sock *vs = vsock_sk(s);
 		struct virtio_vsock_sock *vvs;
 
@@ -631,6 +669,9 @@ void virtio_transport_consume_skb_sent(struct sk_buff *skb, bool consume)
 		spin_unlock_bh(&vvs->tx_lock);
 	}
 
+	if (s && s->sk_type == SOCK_DGRAM)
+		VIRTIO_VSOCK_SKB_CB(skb)->tx_accounted = false;
+
 	if (consume)
 		consume_skb(skb);
 }
@@ -999,7 +1040,49 @@ virtio_transport_dgram_dequeue(struct vsock_sock *vsk,
 			       struct msghdr *msg,
 			       size_t len, int flags)
 {
-	return -EOPNOTSUPP;
+	struct sock *sk = sk_vsock(vsk);
+	struct virtio_vsock_hdr *hdr;
+	struct sk_buff *skb;
+	size_t payload_len;
+	int err;
+
+	if (flags & (MSG_OOB | MSG_ERRQUEUE))
+		return -EOPNOTSUPP;
+
+	skb = skb_recv_datagram(sk, flags, &err);
+	if (!skb)
+		return err;
+
+	hdr = virtio_vsock_hdr(skb);
+	payload_len = skb->len;
+
+	if (payload_len > len) {
+		payload_len = len;
+		msg->msg_flags |= MSG_TRUNC;
+	}
+
+	err = skb_copy_datagram_iter(skb, 0, &msg->msg_iter, payload_len);
+	if (err)
+		goto out;
+
+	if (msg->msg_name) {
+		DECLARE_SOCKADDR(struct sockaddr_vm *, vm_addr, msg->msg_name);
+
+		vsock_addr_init(vm_addr, le64_to_cpu(hdr->src_cid),
+				le32_to_cpu(hdr->src_port));
+		msg->msg_namelen = sizeof(*vm_addr);
+		if (VIRTIO_VSOCK_SKB_CB(skb)->to_host)
+			vm_addr->svm_flags = VMADDR_FLAG_TO_HOST;
+	}
+
+	if (flags & MSG_TRUNC)
+		err = skb->len;
+	else
+		err = payload_len;
+
+out:
+	skb_free_datagram(sk, skb);
+	return err;
 }
 EXPORT_SYMBOL_GPL(virtio_transport_dgram_dequeue);
 
@@ -1202,16 +1285,23 @@ EXPORT_SYMBOL_GPL(virtio_transport_stream_is_active);
 int virtio_transport_dgram_bind(struct vsock_sock *vsk,
 				struct sockaddr_vm *addr)
 {
-	return -EOPNOTSUPP;
+	return vsock_bind_common(vsk, addr);
 }
 EXPORT_SYMBOL_GPL(virtio_transport_dgram_bind);
 
 bool virtio_transport_dgram_allow(struct vsock_sock *vsk, u32 cid, u32 port)
 {
-	return false;
+	return true;
 }
 EXPORT_SYMBOL_GPL(virtio_transport_dgram_allow);
 
+bool virtio_transport_dgram_transport_match(const struct vsock_transport *t)
+{
+	return t && t->dgram_transport_match ==
+		virtio_transport_dgram_transport_match;
+}
+EXPORT_SYMBOL_GPL(virtio_transport_dgram_transport_match);
+
 int virtio_transport_connect(struct vsock_sock *vsk)
 {
 	struct virtio_vsock_pkt_info info = {
@@ -1242,11 +1332,23 @@ EXPORT_SYMBOL_GPL(virtio_transport_shutdown);
 
 int
 virtio_transport_dgram_enqueue(struct vsock_sock *vsk,
+			       const struct vsock_transport *transport,
 			       struct sockaddr_vm *remote_addr,
 			       struct msghdr *msg,
 			       size_t dgram_len)
 {
-	return -EOPNOTSUPP;
+	struct virtio_vsock_pkt_info info = {
+		.op = VIRTIO_VSOCK_OP_RW,
+		.t_ops = container_of(transport, struct virtio_transport, transport),
+		.msg = msg,
+		.pkt_len = dgram_len,
+		.vsk = vsk,
+		.remote_cid = remote_addr->svm_cid,
+		.remote_port = remote_addr->svm_port,
+		.net = sock_net(sk_vsock(vsk)),
+	};
+
+	return virtio_transport_send_pkt_info(vsk, &info);
 }
 EXPORT_SYMBOL_GPL(virtio_transport_dgram_enqueue);
 
@@ -1771,7 +1873,35 @@ virtio_transport_recv_listen(struct sock *sk, struct sk_buff *skb,
 static bool virtio_transport_valid_type(u16 type)
 {
 	return (type == VIRTIO_VSOCK_TYPE_STREAM) ||
-	       (type == VIRTIO_VSOCK_TYPE_SEQPACKET);
+	       (type == VIRTIO_VSOCK_TYPE_SEQPACKET) ||
+	       (type == VIRTIO_VSOCK_TYPE_DGRAM);
+}
+
+static bool
+virtio_transport_dgram_source_allow(struct vsock_sock *vsk,
+				    const struct vsock_transport *transport,
+				    const struct sockaddr_vm *src)
+{
+	const struct vsock_transport *peer_transport;
+	struct sockaddr_vm peer = vsk->remote_addr;
+	bool allow;
+
+	if (!virtio_transport_dgram_transport_match(vsk->transport) ||
+	    !virtio_transport_dgram_transport_match(transport))
+		return false;
+	/* Shutdown preserves the peer; only disconnect removes it. */
+	if (sk_vsock(vsk)->sk_socket->state == SS_UNCONNECTED)
+		return true;
+
+	peer_transport = vsock_dgram_get_transport(vsk, &peer);
+	if (IS_ERR(peer_transport))
+		return false;
+	allow = peer_transport == transport &&
+		src->svm_port == vsk->remote_addr.svm_port &&
+		(src->svm_cid == vsk->remote_addr.svm_cid ||
+		 transport->get_local_cid() == VMADDR_CID_LOCAL);
+	module_put(peer_transport->module);
+	return allow;
 }
 
 /* We are under the virtio-vsock's vsock->rx_lock or vhost-vsock's vq->mutex
@@ -1805,6 +1935,41 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,
 		goto free_pkt;
 	}
 
+	/* Dgram packets are delivered directly to bound sockets without
+	 * going through the connection state machine. Undeliverable
+	 * datagrams are silently dropped (no RST).
+	 */
+	if (le16_to_cpu(hdr->type) == VIRTIO_VSOCK_TYPE_DGRAM) {
+		if (le16_to_cpu(hdr->op) != VIRTIO_VSOCK_OP_RW)
+			goto free_pkt;
+
+		sk = vsock_find_bound_socket_net(&dst, net);
+
+		if (!sk || sk->sk_type != SOCK_DGRAM) {
+			if (sk)
+				sock_put(sk);
+			goto free_pkt;
+		}
+
+		vsk = vsock_sk(sk);
+		lock_sock(sk);
+		if (sock_flag(sk, SOCK_DEAD) ||
+		    !virtio_transport_dgram_source_allow(vsk, &t->transport, &src)) {
+			release_sock(sk);
+			sock_put(sk);
+			goto free_pkt;
+		}
+
+		VIRTIO_VSOCK_SKB_CB(skb)->to_host =
+			t->transport.get_local_cid() > VMADDR_CID_HOST;
+		if (sock_queue_rcv_skb(sk, skb))
+			kfree_skb(skb);
+
+		release_sock(sk);
+		sock_put(sk);
+		return;
+	}
+
 	/* The socket must be in connected or bound table
 	 * otherwise send reset back
 	 */
diff --git a/net/vmw_vsock/vmci_transport.c b/net/vmw_vsock/vmci_transport.c
index 1f186e8f83640a..410deb2493c633 100644
--- a/net/vmw_vsock/vmci_transport.c
+++ b/net/vmw_vsock/vmci_transport.c
@@ -1723,6 +1723,7 @@ static int vmci_transport_dgram_bind(struct vsock_sock *vsk,
 
 static int vmci_transport_dgram_enqueue(
 	struct vsock_sock *vsk,
+	const struct vsock_transport *transport,
 	struct sockaddr_vm *remote_addr,
 	struct msghdr *msg,
 	size_t len)
@@ -1761,6 +1762,14 @@ static int vmci_transport_dgram_enqueue(
 	return err - sizeof(*dg);
 }
 
+static size_t vmci_transport_dgram_payload_len(const struct sk_buff *skb)
+{
+	if (skb->len < sizeof(struct vmci_datagram))
+		return 0;
+
+	return skb->len - sizeof(struct vmci_datagram);
+}
+
 static int vmci_transport_dgram_dequeue(struct vsock_sock *vsk,
 					struct msghdr *msg, size_t len,
 					int flags)
@@ -2075,6 +2084,7 @@ static struct vsock_transport vmci_transport = {
 	.connect = vmci_transport_connect,
 	.dgram_bind = vmci_transport_dgram_bind,
 	.dgram_dequeue = vmci_transport_dgram_dequeue,
+	.dgram_payload_len = vmci_transport_dgram_payload_len,
 	.dgram_enqueue = vmci_transport_dgram_enqueue,
 	.dgram_allow = vmci_transport_dgram_allow,
 	.stream_dequeue = vmci_transport_stream_dequeue,
diff --git a/net/vmw_vsock/vsock_bpf.c b/net/vmw_vsock/vsock_bpf.c
index 9049d26486460c..296e1fa7970b77 100644
--- a/net/vmw_vsock/vsock_bpf.c
+++ b/net/vmw_vsock/vsock_bpf.c
@@ -161,7 +161,8 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore
 	if (!vsk->transport)
 		return -ENODEV;
 
-	if (!vsk->transport->read_skb)
+	/* Datagram receive is not implemented by the sockmap callbacks. */
+	if (sk->sk_type == SOCK_DGRAM || !vsk->transport->read_skb)
 		return -EOPNOTSUPP;
 
 	vsock_bpf_check_needs_rebuild(psock->sk_proto);
diff --git a/net/vmw_vsock/vsock_loopback.c b/net/vmw_vsock/vsock_loopback.c
index 8068d1b6e85181..d7bff1da2b7486 100644
--- a/net/vmw_vsock/vsock_loopback.c
+++ b/net/vmw_vsock/vsock_loopback.c
@@ -77,6 +77,7 @@ static struct virtio_transport loopback_transport = {
 		.dgram_dequeue            = virtio_transport_dgram_dequeue,
 		.dgram_enqueue            = virtio_transport_dgram_enqueue,
 		.dgram_allow              = virtio_transport_dgram_allow,
+		.dgram_transport_match    = virtio_transport_dgram_transport_match,
 
 		.stream_dequeue           = virtio_transport_stream_dequeue,
 		.stream_enqueue           = virtio_transport_stream_enqueue,
diff --git a/tools/testing/vsock/Makefile b/tools/testing/vsock/Makefile
index 88211fd132d23e..78e2cb7b1193dd 100644
--- a/tools/testing/vsock/Makefile
+++ b/tools/testing/vsock/Makefile
@@ -1,7 +1,8 @@
 # SPDX-License-Identifier: GPL-2.0-only
 all: test vsock_perf
 test: vsock_test vsock_diag_test vsock_uring_test
-vsock_test: vsock_test.o vsock_test_zerocopy.o timeout.o control.o util.o msg_zerocopy_common.o
+vsock_test: vsock_test.o vsock_test_zerocopy.o vsock_test_dgram.o \
+	    timeout.o control.o util.o msg_zerocopy_common.o
 vsock_diag_test: vsock_diag_test.o timeout.o control.o util.o
 vsock_perf: vsock_perf.o msg_zerocopy_common.o
 
diff --git a/tools/testing/vsock/README b/tools/testing/vsock/README
index 680ce666ceb56d..be34b032ef1351 100644
--- a/tools/testing/vsock/README
+++ b/tools/testing/vsock/README
@@ -5,7 +5,7 @@ Hyper-V.
 
 The following tests are available:
 
-  * vsock_test - core AF_VSOCK socket functionality
+  * vsock_test - stream, seqpacket and datagram functionality
   * vsock_diag_test - vsock_diag.ko module for listing open sockets
 
 The following prerequisite steps are not automated and must be performed prior
@@ -32,7 +32,7 @@ Invoke test binaries in both directions as follows:
                         --control-port=1234 \
                         --peer-cid=2
   (host)# $TEST_BINARY --mode=client \
-                       --control-port=$GUEST_IP \
+                       --control-host=$GUEST_IP \
                        --control-port=1234 \
                        --peer-cid=3
 
@@ -51,6 +51,25 @@ suite run, e.g.
 
 For more information see Documentation/dev-tools/kmemleak.rst.
 
+Datagram tests
+--------------
+vsock_test includes datagram cases in the client/server runs above. Unsupported
+cases print a skip reason.
+
+Virtio/vhost datagrams require kernel support on both sides and a VMM that
+negotiates VIRTIO_VSOCK_F_DGRAM. Load vsock_loopback on both host and guest for
+the local cases, and make /dev/vsock available. VMCI's dedicated datagram
+transport takes priority when loaded, causing virtio/vhost cases to skip.
+
+Run only the local datagram cases without a peer or TCP control connection:
+
+  ./vsock_test --local
+
+Use --list and --pick <test_id> to select individual cases. The BPF admission
+test (48, requiring BPF privileges) and nested routing test (49, requiring both
+virtio and vhost transports) need explicit selection. The VMCI queue-length
+test (53) runs only with VMCI.
+
 vsock_perf utility
 -------------------
 'vsock_perf' is a simple tool to measure vsock performance. It works in
diff --git a/tools/testing/vsock/util.c b/tools/testing/vsock/util.c
index 299a4e8ecb6345..6ad97403697f18 100644
--- a/tools/testing/vsock/util.c
+++ b/tools/testing/vsock/util.c
@@ -499,47 +499,55 @@ void run_tests(const struct test_case *test_cases,
 	int i;
 
 	for (i = 0; test_cases[i].name; i++) {
+		const struct test_case *test = &test_cases[i];
 		void (*run)(const struct test_opts *opts);
+		const char *reason;
 		char *line;
 
-		printf("%d - %s...", i, test_cases[i].name);
+		if (opts->local && !test->local)
+			continue;
+		printf("%d - %s...", i, test->name);
 		fflush(stdout);
 
-		/* Full barrier before executing the next test.  This
-		 * ensures that client and server are executing the
-		 * same test case.  In particular, it means whoever is
-		 * faster will not see the peer still executing the
-		 * last test.  This is important because port numbers
-		 * can be used by multiple test cases.
-		 */
-		if (test_cases[i].skip)
-			control_writeln("SKIP");
-		else
-			control_writeln("NEXT");
-
-		line = control_readln();
-		if (control_cmpln(line, "SKIP", false) || test_cases[i].skip) {
-
-			printf("skipped\n");
-
+		if (opts->local) {
+			if (test->skip) {
+				printf("skipped\n");
+				continue;
+			}
+		} else {
+			/* Agree on explicit selection before any capability check
+			 * or test can exchange messages on the control connection.
+			 */
+			control_writeln(test->skip ? "SKIP" : "NEXT");
+			line = control_readln();
+			if (control_cmpln(line, "SKIP", false) || test->skip) {
+				printf("skipped\n");
+				free(line);
+				continue;
+			}
+			control_cmpln(line, "NEXT", true);
 			free(line);
-			continue;
 		}
 
-		control_cmpln(line, "NEXT", true);
-		free(line);
-
-		if (opts->mode == TEST_MODE_CLIENT)
-			run = test_cases[i].run_client;
+		if (test->check) {
+			reason = test->check(opts);
+			if (reason) {
+				printf("skipped: %s\n", reason);
+				continue;
+			}
+		}
+		if (opts->local || opts->mode == TEST_MODE_CLIENT)
+			run = test->run_client;
 		else
-			run = test_cases[i].run_server;
-
+			run = test->run_server;
 		if (run)
 			run(opts);
-
 		printf("ok\n");
 	}
 
+	if (opts->local)
+		return;
+
 	printf("All tests have been executed. Waiting other peer...");
 	fflush(stdout);
 
diff --git a/tools/testing/vsock/util.h b/tools/testing/vsock/util.h
index 9dabb547021edc..e23290e7dcc64f 100644
--- a/tools/testing/vsock/util.h
+++ b/tools/testing/vsock/util.h
@@ -51,6 +51,10 @@ struct test_opts {
 	enum test_mode mode;
 	unsigned int peer_cid;
 	unsigned int peer_port;
+	unsigned int bind_cid;
+	bool local;
+	bool to_host;
+	bool expect_to_host;
 };
 
 /* A test case definition.  Test functions must print failures to stderr and
@@ -66,6 +70,12 @@ struct test_case {
 	void (*run_server)(const struct test_opts *opts);
 
 	bool skip;
+	bool local; /* Can run without a control connection */
+
+	/* Return a skip reason or NULL after the selection barrier. Checks
+	 * that coordinate over the control channel must agree on both peers.
+	 */
+	const char *(*check)(const struct test_opts *opts);
 };
 
 void init_signals(void);
diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c
index 10b9074fb87f7f..f4ff6a42a9cb17 100644
--- a/tools/testing/vsock/vsock_test.c
+++ b/tools/testing/vsock/vsock_test.c
@@ -27,6 +27,7 @@
 #include <linux/sockios.h>
 
 #include "vsock_test_zerocopy.h"
+#include "vsock_test_dgram.h"
 #include "timeout.h"
 #include "control.h"
 #include "util.h"
@@ -2911,6 +2912,95 @@ static struct test_case test_cases[] = {
 	{
 		.name = "SO_INQ option validation",
 		.run_client = test_so_inq_validation_client,
+	},
+		.name = "SOCK_DGRAM autobind, duplicate bind and wildcard destination",
+		.run_client = test_dgram_bind,
+		.run_server = test_dgram_bind,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM queue ioctls, poll, peek, truncation and empty messages",
+		.run_client = test_dgram_receive,
+		.run_server = test_dgram_receive,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM unsupported receive flags preserve normal data",
+		.run_client = test_dgram_receive_flags,
+		.run_server = test_dgram_receive_flags,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM socket filter trims receive length",
+		.run_client = test_dgram_filter,
+		.run_server = test_dgram_filter,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM connect, disconnect and shutdown peer filtering",
+		.run_client = test_dgram_connect,
+		.run_server = test_dgram_connect,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM explicit loopback source CIDs and reply addresses",
+		.run_client = test_dgram_source_cid,
+		.run_server = test_dgram_source_cid,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM connected stream/seqpacket cannot shadow datagrams",
+		.run_client = test_dgram_bound_lookup,
+		.run_server = test_dgram_bound_lookup,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM sockmap and sockhash reject datagrams (BPF privileges)",
+		.run_client = test_dgram_sockmap,
+		.run_server = test_dgram_sockmap,
+		.skip = true,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM nested CID_HOST loopback rejection (nested VM only)",
+		.run_client = test_dgram_nested_source,
+		.run_server = test_dgram_nested_source,
+		.skip = true,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM bidirectional sizes, 64 KiB boundaries and RX refill",
+		.run_client = test_dgram_sizes,
+		.run_server = test_dgram_sizes,
+		.check = vsock_dgram_check_peer,
+	},
+	{
+		.name = "SOCK_DGRAM connected socket alternates loopback and peer routes",
+		.run_client = test_dgram_routes,
+		.run_server = test_dgram_routes,
+		.check = vsock_dgram_check_routes,
+	},
+	{
+		.name = "SOCK_DGRAM peer filtering and disconnect across transports",
+		.run_client = test_dgram_peer_filter,
+		.run_server = test_dgram_peer_filter,
+		.check = vsock_dgram_check_peer,
+	},
+	{
+		.name = "SOCK_DGRAM VMCI ioctl(SIOCINQ) payload length",
+		.run_client = test_dgram_vmci_inq,
+		.run_server = test_dgram_vmci_inq,
+		.check = vsock_dgram_check_vmci,
+		.local = true,
 	},
 	{},
 };
@@ -2957,6 +3047,26 @@ static const struct option longopts[] = {
 		.has_arg = required_argument,
 		.val = 't',
 	},
+	{
+		.name = "local",
+		.has_arg = no_argument,
+		.val = 'L',
+	},
+	{
+		.name = "bind-cid",
+		.has_arg = required_argument,
+		.val = 'b',
+	},
+	{
+		.name = "to-host",
+		.has_arg = no_argument,
+		.val = 'T',
+	},
+	{
+		.name = "expect-to-host",
+		.has_arg = no_argument,
+		.val = 'E',
+	},
 	{
 		.name = "help",
 		.has_arg = no_argument,
@@ -2972,8 +3082,10 @@ static void usage(void)
 		"  Server: vsock_test --control-port=1234 --mode=server --peer-cid=3\n"
 		"  Client: vsock_test --control-host=192.168.0.1 --control-port=1234 --mode=client --peer-cid=2\n"
 		"\n"
-		"Run vsock.ko tests.  Must be launched in both guest\n"
-		"and host.  One side must use --mode=client and\n"
+		"  Local: vsock_test --local [--pick=<test_id>]\n"
+		"\n"
+		"Run vsock.ko tests. Paired tests must be launched in both guest\n"
+		"and host. One side must use --mode=client and\n"
 		"the other side must use --mode=server.\n"
 		"\n"
 		"A TCP control socket connection is used to coordinate tests\n"
@@ -2986,8 +3098,14 @@ static void usage(void)
 		"specified with --peer-port=<port> (or the default port)\n"
 		"and the next one.\n"
 		"\n"
+		"--local runs local datagram cases without a control connection.\n"
+		"\n"
 		"Options:\n"
 		"  --help                 This help message\n"
+		"  --local                Run local datagram cases without a peer\n"
+		"  --bind-cid <cid>       Source CID for peer datagram tests [default: ANY]\n"
+		"  --to-host              Route peer datagrams through the frontend\n"
+		"  --expect-to-host       Expect the upstream datagram source flag\n"
 		"  --control-host <host>  Server IP address to connect to\n"
 		"  --control-port <port>  Server port to listen on/connect to\n"
 		"  --mode client|server   Server or client mode\n"
@@ -3011,7 +3129,10 @@ int main(int argc, char **argv)
 		.mode = TEST_MODE_UNSET,
 		.peer_cid = VMADDR_CID_ANY,
 		.peer_port = DEFAULT_PEER_PORT,
+		.bind_cid = VMADDR_CID_ANY,
 	};
+	bool picked = false;
+	size_t i;
 
 	srand(time(NULL));
 	init_signals();
@@ -3023,6 +3144,18 @@ int main(int argc, char **argv)
 			break;
 
 		switch (opt) {
+		case 'L':
+			opts.local = true;
+			break;
+		case 'b':
+			opts.bind_cid = parse_cid(optarg);
+			break;
+		case 'T':
+			opts.to_host = true;
+			break;
+		case 'E':
+			opts.expect_to_host = true;
+			break;
 		case 'H':
 			control_host = optarg;
 			break;
@@ -3055,6 +3188,7 @@ int main(int argc, char **argv)
 		case 't':
 			pick_test(test_cases, ARRAY_SIZE(test_cases) - 1,
 				  optarg);
+			picked = true;
 			break;
 		case '?':
 		default:
@@ -3062,6 +3196,22 @@ int main(int argc, char **argv)
 		}
 	}
 
+	if (optind != argc)
+		usage();
+	if (opts.local) {
+		if (opts.mode != TEST_MODE_UNSET || control_host || control_port)
+			usage();
+		if (picked) {
+			for (i = 0; test_cases[i].name; i++) {
+				if (!test_cases[i].skip && !test_cases[i].local) {
+					fprintf(stderr, "test %zu requires paired mode\n", i);
+					exit(EXIT_FAILURE);
+				}
+			}
+		}
+		run_tests(test_cases, &opts);
+		return EXIT_SUCCESS;
+	}
 	if (!control_port)
 		usage();
 	if (opts.mode == TEST_MODE_UNSET)
diff --git a/tools/testing/vsock/vsock_test_dgram.c b/tools/testing/vsock/vsock_test_dgram.c
new file mode 100644
index 00000000000000..d818f43176dc9f
--- /dev/null
+++ b/tools/testing/vsock/vsock_test_dgram.c
@@ -0,0 +1,809 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Datagram feature tests for vsock. */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/bpf.h>
+#include <linux/filter.h>
+#include <linux/sockios.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/syscall.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "control.h"
+#include "timeout.h"
+#include "vsock_test_dgram.h"
+
+#define MAX_DGRAM (64 * 1024)
+
+static void fail(const char *fmt, ...)
+{
+	va_list ap;
+
+	fprintf(stderr, "\nFAIL: ");
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	fputc('\n', stderr);
+	exit(EXIT_FAILURE);
+}
+
+static void check(int ret, const char *operation)
+{
+	if (ret < 0)
+		fail("%s: %s", operation, strerror(errno));
+}
+
+static void expect_error(ssize_t ret, int error, const char *operation)
+{
+	if (ret != -1 || errno != error)
+		fail("%s: expected %s, got %zd (%s)", operation,
+		     strerror(error), ret, ret == -1 ? strerror(errno) : "success");
+}
+
+static int64_t now_ms(void)
+{
+	struct timespec ts;
+
+	check(clock_gettime(CLOCK_MONOTONIC, &ts), "clock_gettime");
+	return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
+}
+
+static short poll_fd(int fd, short events, int milliseconds)
+{
+	struct pollfd pfd = { .fd = fd, .events = events };
+	int ret;
+
+	do {
+		ret = poll(&pfd, 1, milliseconds);
+	} while (ret < 0 && errno == EINTR);
+	check(ret, "poll");
+	if (pfd.revents & (POLLERR | POLLNVAL))
+		fail("unexpected poll events %#x", pfd.revents);
+	return pfd.revents;
+}
+
+static void wait_readable(int fd)
+{
+	if (!(poll_fd(fd, POLLIN, TIMEOUT * 1000) & POLLIN))
+		fail("waiting for socket readability timed out");
+}
+
+static struct sockaddr_vm address(unsigned int cid, unsigned int port)
+{
+	return (struct sockaddr_vm) {
+		.svm_family = AF_VSOCK,
+		.svm_cid = cid,
+		.svm_port = port,
+	};
+}
+
+static struct sockaddr_vm sock_name(int fd, bool peer)
+{
+	struct sockaddr_vm addr;
+	socklen_t len = sizeof(addr);
+	int ret;
+
+	if (peer)
+		ret = getpeername(fd, (struct sockaddr *)&addr, &len);
+	else
+		ret = getsockname(fd, (struct sockaddr *)&addr, &len);
+	check(ret, peer ? "getpeername" : "getsockname");
+	if (len != sizeof(addr) || addr.svm_family != AF_VSOCK)
+		fail("invalid socket address");
+	return addr;
+}
+
+static unsigned int local_cid(void)
+{
+	unsigned int cid;
+	int fd;
+
+	fd = open("/dev/vsock", O_RDONLY | O_CLOEXEC);
+	check(fd, "open /dev/vsock");
+	check(ioctl(fd, IOCTL_VM_SOCKETS_GET_LOCAL_CID, &cid), "get local CID");
+	close(fd);
+	if (cid == VMADDR_CID_ANY || cid <= VMADDR_CID_LOCAL)
+		fail("a virtio or vhost transport is required (local CID %u)", cid);
+	return cid;
+}
+
+static void bind_fd(int fd, unsigned int cid, unsigned int port)
+{
+	struct sockaddr_vm addr = address(cid, port);
+
+	check(bind(fd, (struct sockaddr *)&addr, sizeof(addr)), "bind");
+}
+
+static int dgram_socket(void)
+{
+	int fd = socket(AF_VSOCK, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+	int rcvbuf = 1024 * 1024;
+
+	check(fd, "socket(AF_VSOCK, SOCK_DGRAM): check transport/feature support");
+	check(setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)), "SO_RCVBUF");
+	return fd;
+}
+
+enum dgram_capability {
+	DGRAM_SOCKET = 1 << 0,
+	DGRAM_VIRTIO = 1 << 1,
+	DGRAM_VMCI = 1 << 2,
+	DGRAM_LOOPBACK = 1 << 3,
+	DGRAM_PEER = 1 << 4,
+};
+
+static bool dgram_unavailable(int error)
+{
+	return error == EAFNOSUPPORT || error == EPROTONOSUPPORT ||
+	       error == ESOCKTNOSUPPORT || error == ENODEV;
+}
+
+static unsigned int dgram_capabilities(const struct test_opts *opts,
+				       unsigned int needs)
+{
+	unsigned int caps = DGRAM_SOCKET;
+	struct sockaddr_vm peer;
+	int fd, local_fd;
+
+	fd = socket(AF_VSOCK, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+	if (fd < 0) {
+		if (dgram_unavailable(errno))
+			return 0;
+		check(fd, "probe datagram socket");
+	}
+	/* VMCI registers the dedicated datagram transport when loaded. */
+	if (!access("/sys/module/vmw_vsock_vmci_transport", F_OK))
+		caps |= DGRAM_VMCI;
+	else
+		caps |= DGRAM_VIRTIO;
+
+	if ((needs & DGRAM_PEER) && (caps & DGRAM_VIRTIO)) {
+		bind_fd(fd, opts->bind_cid, VMADDR_PORT_ANY);
+		peer = address(opts->peer_cid, opts->peer_port);
+		if (opts->to_host)
+			peer.svm_flags = VMADDR_FLAG_TO_HOST;
+		if (!connect(fd, (struct sockaddr *)&peer, sizeof(peer)))
+			caps |= DGRAM_PEER;
+		else if (!dgram_unavailable(errno) && errno != EINVAL)
+			check(-1, "probe peer datagram route");
+	}
+	close(fd);
+
+	if ((needs & DGRAM_LOOPBACK) && (caps & DGRAM_VIRTIO)) {
+		/* Binding a stream socket to CID_LOCAL probes registration of
+		 * the loopback transport without sending a packet to the host.
+		 */
+		local_fd = vsock_bind_try(VMADDR_CID_LOCAL, VMADDR_PORT_ANY, SOCK_STREAM);
+		if (local_fd >= 0) {
+			caps |= DGRAM_LOOPBACK;
+			close(local_fd);
+		} else if (errno != EADDRNOTAVAIL) {
+			check(local_fd, "probe loopback transport");
+		}
+	}
+	return caps;
+}
+
+static const char *dgram_check(const struct test_opts *opts, unsigned int needs)
+{
+	unsigned int local = dgram_capabilities(opts, needs);
+	unsigned int both = local;
+
+	/* run_tests() has already synchronized explicit --pick/--skip
+	 * selection. Both peers can now safely exchange capabilities.
+	 */
+	if (!opts->local) {
+		control_writeulong(local);
+		both &= control_readulong();
+	}
+	if (!(both & DGRAM_SOCKET))
+		return local & DGRAM_SOCKET ?
+			"datagrams unavailable at peer" : "datagrams unavailable locally";
+	if ((needs & DGRAM_VIRTIO) && !(both & DGRAM_VIRTIO))
+		return "requires virtio/vhost datagram transports on both peers";
+	if ((needs & DGRAM_VMCI) && !(both & DGRAM_VMCI))
+		return "requires VMCI datagram transports on both peers";
+	if ((needs & DGRAM_LOOPBACK) && !(both & DGRAM_LOOPBACK))
+		return "load vsock_loopback on both peers";
+	if ((needs & DGRAM_PEER) && !(both & DGRAM_PEER))
+		return "peer route does not support datagrams";
+	return NULL;
+}
+
+const char *vsock_dgram_check_local(const struct test_opts *opts)
+{
+	return dgram_check(opts, DGRAM_VIRTIO | DGRAM_LOOPBACK);
+}
+
+const char *vsock_dgram_check_peer(const struct test_opts *opts)
+{
+	return dgram_check(opts, DGRAM_VIRTIO | DGRAM_PEER);
+}
+
+const char *vsock_dgram_check_routes(const struct test_opts *opts)
+{
+	return dgram_check(opts, DGRAM_VIRTIO | DGRAM_PEER | DGRAM_LOOPBACK);
+}
+
+const char *vsock_dgram_check_vmci(const struct test_opts *opts)
+{
+	return dgram_check(opts, DGRAM_VMCI);
+}
+
+static int bound_socket(unsigned int cid)
+{
+	int fd = dgram_socket();
+
+	bind_fd(fd, cid, VMADDR_PORT_ANY);
+	return fd;
+}
+
+static struct sockaddr_vm local_address(int fd)
+{
+	struct sockaddr_vm addr = sock_name(fd, false);
+
+	addr.svm_cid = VMADDR_CID_LOCAL;
+	return addr;
+}
+
+static void connect_fd(int fd, const struct sockaddr_vm *addr)
+{
+	check(connect(fd, (const struct sockaddr *)addr, sizeof(*addr)), "connect");
+}
+
+static void disconnect_fd(int fd)
+{
+	struct sockaddr_vm addr = { .svm_family = AF_UNSPEC };
+	socklen_t len = sizeof(addr);
+
+	check(connect(fd, (struct sockaddr *)&addr, sizeof(addr)), "disconnect");
+	expect_error(getpeername(fd, (struct sockaddr *)&addr, &len),
+		     ENOTCONN, "getpeername after disconnect");
+}
+
+static int queue_bytes(int fd, unsigned long op)
+{
+	int bytes;
+
+	check(ioctl(fd, op, &bytes), "queue ioctl");
+	if (bytes < 0)
+		fail("negative queue length: %d", bytes);
+	return bytes;
+}
+
+static void expect_inq(int fd, int expected)
+{
+	int actual = queue_bytes(fd, SIOCINQ);
+
+	if (actual != expected)
+		fail("SIOCINQ: expected %d, got %d", expected, actual);
+}
+
+static void wait_sent(int fd)
+{
+	int64_t deadline = now_ms() + TIMEOUT * 1000;
+
+	while (queue_bytes(fd, SIOCOUTQ)) {
+		if (now_ms() >= deadline)
+			fail("SIOCOUTQ did not return to zero");
+		usleep(1000);
+	}
+}
+
+static void send_packet(int fd, const struct sockaddr_vm *addr,
+			const void *buf, size_t len)
+{
+	int64_t deadline = now_ms() + TIMEOUT * 1000;
+	ssize_t ret;
+
+	for (;;) {
+		ret = sendto(fd, buf, len, MSG_DONTWAIT | MSG_NOSIGNAL,
+			     (const struct sockaddr *)addr, addr ? sizeof(*addr) : 0);
+		if (ret >= 0)
+			break;
+		if (errno != EAGAIN && errno != EINTR)
+			fail("sendto(%zu bytes): %s", len, strerror(errno));
+		if (now_ms() >= deadline)
+			fail("sendto(%zu bytes) timed out", len);
+		poll_fd(fd, POLLOUT, 1);
+	}
+	if (ret != len)
+		fail("partial datagram send: %zd/%zu", ret, len);
+}
+
+static struct sockaddr_vm recv_packet(int fd, void *buf, size_t capacity,
+				      int flags, size_t expected, bool truncated)
+{
+	struct sockaddr_vm src = { 0 };
+	struct iovec iov = { .iov_base = buf, .iov_len = capacity };
+	struct msghdr msg = {
+		.msg_name = &src,
+		.msg_namelen = sizeof(src),
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	ssize_t ret;
+
+	wait_readable(fd);
+	ret = recvmsg(fd, &msg, flags | MSG_DONTWAIT);
+	check(ret, "recvmsg");
+	if (ret != expected || !!(msg.msg_flags & MSG_TRUNC) != truncated)
+		fail("recvmsg: expected %zu bytes, trunc=%d; got %zd, flags=%#x",
+		     expected, truncated, ret, msg.msg_flags);
+	if (msg.msg_namelen != sizeof(src) || src.svm_family != AF_VSOCK)
+		fail("invalid recvmsg source address");
+	return src;
+}
+
+static void expect_empty(int fd)
+{
+	char byte;
+
+	if (poll_fd(fd, POLLIN, 0) & POLLIN)
+		fail("unexpected queued datagram");
+	expect_error(recv(fd, &byte, 1, MSG_DONTWAIT), EAGAIN, "empty recv");
+	expect_inq(fd, 0);
+}
+
+void test_dgram_bind(const struct test_opts *opts)
+{
+	int rx = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm dest = local_address(rx);
+	int tx = dgram_socket();
+	int other = dgram_socket();
+	struct sockaddr_vm src;
+	char byte;
+
+	dest.svm_cid = VMADDR_CID_ANY;
+	expect_error(bind(other, (struct sockaddr *)&dest, sizeof(dest)),
+		     EADDRINUSE, "duplicate bind");
+	send_packet(tx, &dest, "A", 1);
+	src = recv_packet(rx, &byte, 1, 0, 1, false);
+	if (byte != 'A' || src.svm_port != sock_name(tx, false).svm_port ||
+	    src.svm_cid != VMADDR_CID_LOCAL || src.svm_port < 1024)
+		fail("autobind or wildcard destination returned an invalid source");
+	close(rx);
+	bind_fd(other, VMADDR_CID_ANY, dest.svm_port);
+	close(other);
+	close(tx);
+}
+
+void test_dgram_receive(const struct test_opts *opts)
+{
+	int rx = bound_socket(VMADDR_CID_ANY);
+	int tx = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm dest = local_address(rx);
+	char buf[80];
+	char first[31], second[79];
+
+	memset(first, 'A', sizeof(first));
+	memset(second, 'B', sizeof(second));
+	expect_empty(rx);
+	if (!(poll_fd(tx, POLLOUT, 0) & POLLOUT))
+		fail("new socket is not writable");
+	if (queue_bytes(tx, SIOCOUTQ))
+		fail("new socket has pending output");
+	send_packet(tx, &dest, first, sizeof(first));
+	send_packet(tx, &dest, second, sizeof(second));
+	wait_readable(rx);
+	expect_inq(rx, sizeof(first));
+	recv_packet(rx, buf, 5, MSG_PEEK, 5, true);
+	if (memcmp(buf, first, 5))
+		fail("MSG_PEEK payload mismatch");
+	expect_inq(rx, sizeof(first));
+	recv_packet(rx, buf, 5, MSG_TRUNC, sizeof(first), true);
+	wait_readable(rx);
+	expect_inq(rx, sizeof(second));
+	recv_packet(rx, buf, sizeof(buf), 0, sizeof(second), false);
+	if (memcmp(buf, second, sizeof(second)))
+		fail("datagram boundary or payload mismatch");
+	send_packet(tx, &dest, "", 0);
+	wait_readable(rx);
+	expect_inq(rx, 0);
+	recv_packet(rx, buf, sizeof(buf), MSG_PEEK, 0, false);
+	recv_packet(rx, buf, sizeof(buf), 0, 0, false);
+	send_packet(tx, &dest, first, sizeof(first));
+	recv_packet(rx, buf, 0, MSG_TRUNC, sizeof(first), true);
+	expect_empty(rx);
+	wait_sent(tx);
+	close(tx);
+	close(rx);
+}
+
+void test_dgram_receive_flags(const struct test_opts *opts)
+{
+	int rx = bound_socket(VMADDR_CID_ANY);
+	int tx = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm dest = local_address(rx);
+	const int flags[] = { MSG_OOB, MSG_ERRQUEUE };
+	char byte;
+	size_t i;
+
+	send_packet(tx, &dest, "A", 1);
+	wait_readable(rx);
+	for (i = 0; i < ARRAY_SIZE(flags); i++) {
+		expect_error(recv(rx, &byte, 1, flags[i] | MSG_DONTWAIT),
+			     EOPNOTSUPP, "unsupported receive flag");
+		expect_inq(rx, 1);
+	}
+	recv_packet(rx, &byte, 1, 0, 1, false);
+	if (byte != 'A')
+		fail("unsupported flags consumed the normal packet");
+	close(tx);
+	close(rx);
+}
+
+void test_dgram_vmci_inq(const struct test_opts *opts)
+{
+	int rx = bound_socket(VMADDR_CID_ANY);
+	int tx = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm dest = sock_name(rx, false);
+	char first[31], second[79], buf[80];
+
+	memset(first, 'A', sizeof(first));
+	memset(second, 'B', sizeof(second));
+	/* CID_ANY is resolved by the dedicated transport to its local CID. */
+	dest.svm_cid = VMADDR_CID_ANY;
+	send_packet(tx, &dest, first, sizeof(first));
+	send_packet(tx, &dest, second, sizeof(second));
+	wait_readable(rx);
+	expect_inq(rx, sizeof(first));
+	recv_packet(rx, buf, sizeof(buf), 0, sizeof(first), false);
+	if (memcmp(buf, first, sizeof(first)))
+		fail("VMCI first payload mismatch");
+	wait_readable(rx);
+	expect_inq(rx, sizeof(second));
+	recv_packet(rx, buf, sizeof(buf), 0, sizeof(second), false);
+	if (memcmp(buf, second, sizeof(second)))
+		fail("VMCI second payload mismatch");
+	send_packet(tx, &dest, "", 0);
+	wait_readable(rx);
+	expect_inq(rx, 0);
+	recv_packet(rx, buf, sizeof(buf), 0, 0, false);
+	expect_empty(rx);
+	close(tx);
+	close(rx);
+}
+
+void test_dgram_filter(const struct test_opts *opts)
+{
+	struct sock_filter insn[] = { BPF_STMT(BPF_RET | BPF_K, 7) };
+	struct sock_fprog prog = { .len = ARRAY_SIZE(insn), .filter = insn };
+	int rx = bound_socket(VMADDR_CID_ANY);
+	int tx = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm dest = local_address(rx);
+	char data[100], buf[100];
+
+	memset(data, 'F', sizeof(data));
+	check(setsockopt(rx, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)),
+	      "SO_ATTACH_FILTER");
+	send_packet(tx, &dest, data, sizeof(data));
+	wait_readable(rx);
+	expect_inq(rx, 7);
+	recv_packet(rx, buf, 1, MSG_PEEK | MSG_TRUNC, 7, true);
+	expect_inq(rx, 7);
+	memset(buf, 0, sizeof(buf));
+	recv_packet(rx, buf, sizeof(buf), MSG_TRUNC, 7, false);
+	if (memcmp(buf, data, 7) || buf[7])
+		fail("filtered receive copied beyond the filtered payload");
+	send_packet(tx, &dest, data, sizeof(data));
+	recv_packet(rx, buf, 1, MSG_TRUNC, 7, true);
+	expect_empty(rx);
+	close(tx);
+	close(rx);
+}
+
+static void check_peer_filter(int rx, int allowed, int stray)
+{
+	struct sockaddr_vm dest = local_address(rx);
+	char byte;
+
+	/* Both packets use the same ordered loopback worker. */
+	send_packet(stray, &dest, "B", 1);
+	send_packet(allowed, &dest, "A", 1);
+	recv_packet(rx, &byte, 1, 0, 1, false);
+	if (byte != 'A')
+		fail("connected socket accepted a different peer");
+	expect_empty(rx);
+}
+
+void test_dgram_connect(const struct test_opts *opts)
+{
+	int rx = bound_socket(VMADDR_CID_ANY);
+	int allowed = bound_socket(VMADDR_CID_ANY);
+	int stray = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm peer = local_address(allowed);
+	struct sockaddr_vm dest = local_address(rx);
+	char byte;
+
+	connect_fd(rx, &peer);
+	check_peer_filter(rx, allowed, stray);
+	disconnect_fd(rx);
+	send_packet(stray, &dest, "B", 1);
+	recv_packet(rx, &byte, 1, 0, 1, false);
+	if (byte != 'B')
+		fail("disconnect did not remove peer filtering");
+	connect_fd(rx, &peer);
+	check(shutdown(rx, SHUT_WR), "shutdown SHUT_WR");
+	if (poll_fd(rx, POLLOUT, 0) & POLLOUT)
+		fail("write-shutdown socket is still writable");
+	check_peer_filter(rx, allowed, stray);
+	check(shutdown(rx, SHUT_RD), "shutdown SHUT_RD");
+	if (!(poll_fd(rx, POLLIN | POLLRDHUP, 0) & POLLRDHUP))
+		fail("read shutdown did not report POLLRDHUP");
+	close(stray);
+	close(allowed);
+	close(rx);
+}
+
+void test_dgram_source_cid(const struct test_opts *opts)
+{
+	unsigned int cids[] = { VMADDR_CID_LOCAL, local_cid() };
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(cids); i++) {
+		int rx = bound_socket(VMADDR_CID_ANY);
+		int tx = bound_socket(cids[i]);
+		struct sockaddr_vm dest = local_address(rx);
+		struct sockaddr_vm src;
+		char byte;
+
+		send_packet(tx, &dest, "A", 1);
+		src = recv_packet(rx, &byte, 1, 0, 1, false);
+		if (src.svm_cid != cids[i] || src.svm_flags ||
+		    src.svm_port != sock_name(tx, false).svm_port)
+			fail("explicit loopback source CID %u was not preserved", cids[i]);
+		send_packet(rx, &src, "R", 1);
+		recv_packet(tx, &byte, 1, 0, 1, false);
+		if (byte != 'R')
+			fail("reply to explicit source CID failed");
+		close(tx);
+		close(rx);
+	}
+}
+
+void test_dgram_bound_lookup(const struct test_opts *opts)
+{
+	const int types[] = { SOCK_STREAM, SOCK_SEQPACKET };
+	unsigned int cid = local_cid();
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(types); i++) {
+		int listener = vsock_bind(VMADDR_CID_ANY, VMADDR_PORT_ANY, types[i]);
+		int stream = vsock_bind(VMADDR_CID_LOCAL, VMADDR_PORT_ANY, types[i]);
+		struct sockaddr_vm listen_addr = local_address(listener);
+		struct sockaddr_vm src = sock_name(stream, false);
+		struct sockaddr_vm dest;
+		int accepted, tx, rx;
+		char byte;
+
+		check(listen(listener, 1), "listen");
+		if (vsock_connect_fd(stream, listen_addr.svm_cid, listen_addr.svm_port))
+			fail("%s loopback connect: %s",
+			     types[i] == SOCK_STREAM ? "stream" : "seqpacket", strerror(errno));
+		wait_readable(listener);
+		accepted = accept4(listener, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);
+		check(accepted, "accept");
+		if (sock_name(accepted, true).svm_cid != VMADDR_CID_LOCAL ||
+		    sock_name(accepted, true).svm_port != src.svm_port)
+			fail("unexpected accepted loopback peer");
+		close(listener);
+		rx = dgram_socket();
+		bind_fd(rx, cid, src.svm_port);
+		dest = sock_name(rx, false);
+		tx = dgram_socket();
+		/* Reuse the listener's port for the datagram source. The receive
+		 * socket uses the stream client's port at another local CID.
+		 * Connected lookup ignores the destination CID, so it would find
+		 * the stream client before the bound datagram socket.
+		 */
+		bind_fd(tx, VMADDR_CID_LOCAL, listen_addr.svm_port);
+		send_packet(tx, &dest, "D", 1);
+		recv_packet(rx, &byte, 1, 0, 1, false);
+		if (byte != 'D' || (poll_fd(stream, POLLIN, 0) & POLLIN) ||
+		    (poll_fd(accepted, POLLIN, 0) & POLLIN))
+			fail("connected socket shadowed the reused datagram port");
+		send_byte(stream, 1, 0);
+		wait_readable(accepted);
+		recv_byte(accepted, 1, 0);
+		send_byte(accepted, 1, 0);
+		wait_readable(stream);
+		recv_byte(stream, 1, 0);
+		close(tx);
+		close(rx);
+		close(accepted);
+		close(stream);
+	}
+}
+
+void test_dgram_sockmap(const struct test_opts *opts)
+{
+	const int types[] = { BPF_MAP_TYPE_SOCKMAP, BPF_MAP_TYPE_SOCKHASH };
+	int fd = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm addr = local_address(fd);
+	uint64_t value = fd;
+	uint32_t key = 0;
+	size_t i;
+
+	connect_fd(fd, &addr);
+	for (i = 0; i < ARRAY_SIZE(types); i++) {
+		union bpf_attr attr = {
+			.map_type = types[i],
+			.key_size = sizeof(key),
+			.value_size = sizeof(value),
+			.max_entries = 1,
+		};
+		int map = syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
+
+		check(map, "BPF_MAP_CREATE (requires BPF privileges and CONFIG_BPF_SYSCALL)");
+		memset(&attr, 0, sizeof(attr));
+		attr.map_fd = map;
+		attr.key = (uintptr_t)&key;
+		attr.value = (uintptr_t)&value;
+		attr.flags = BPF_ANY;
+		expect_error(syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)),
+			     EOPNOTSUPP, "datagram sockmap admission");
+		close(map);
+	}
+	close(fd);
+}
+
+void test_dgram_nested_source(const struct test_opts *opts)
+{
+	int rx, tx;
+	struct sockaddr_vm dest;
+
+	if (local_cid() <= VMADDR_CID_HOST)
+		fail("this test requires both a guest frontend and vhost_vsock");
+	rx = bound_socket(VMADDR_CID_LOCAL);
+	tx = bound_socket(VMADDR_CID_HOST);
+	dest = local_address(rx);
+	expect_error(sendto(tx, "A", 1, MSG_NOSIGNAL,
+			    (struct sockaddr *)&dest, sizeof(dest)),
+		     EADDRNOTAVAIL, "nested CID_HOST loopback send");
+	expect_error(connect(tx, (struct sockaddr *)&dest, sizeof(dest)),
+		     EADDRNOTAVAIL, "nested CID_HOST loopback connect");
+	if (queue_bytes(tx, SIOCOUTQ))
+		fail("rejected route left pending output");
+	expect_empty(rx);
+	close(tx);
+	close(rx);
+}
+
+static int peer_socket(const struct test_opts *opts, struct sockaddr_vm *peer)
+{
+	int fd = bound_socket(opts->bind_cid);
+
+	control_writeulong(sock_name(fd, false).svm_port);
+	*peer = address(opts->peer_cid, control_readulong());
+	if (opts->to_host)
+		peer->svm_flags = VMADDR_FLAG_TO_HOST;
+	return fd;
+}
+
+static void check_source(const struct test_opts *opts,
+			 const struct sockaddr_vm *peer, const struct sockaddr_vm *src)
+{
+	bool upstream = opts->expect_to_host || opts->peer_cid == VMADDR_CID_HOST;
+
+	if (src->svm_cid != peer->svm_cid || src->svm_port != peer->svm_port ||
+	    src->svm_flags != (upstream ? VMADDR_FLAG_TO_HOST : 0))
+		fail("source expected %u:%u flags=%u, got %u:%u flags=%u",
+		     peer->svm_cid, peer->svm_port, upstream ? VMADDR_FLAG_TO_HOST : 0,
+		     src->svm_cid, src->svm_port, src->svm_flags);
+}
+
+static void fill_payload(char *buf, size_t len, unsigned int seed)
+{
+	size_t i;
+
+	for (i = 0; i < len; i++)
+		buf[i] = (i * 131 + seed) & 0xff;
+}
+
+void test_dgram_sizes(const struct test_opts *opts)
+{
+	const size_t sizes[] = { 0, 1, 7, 255, 4095, 4096, 4097,
+				8192, 32768, 65535, MAX_DGRAM };
+	char *data = malloc(MAX_DGRAM + 1);
+	char *buf = malloc(MAX_DGRAM + 1);
+	struct sockaddr_vm peer, src;
+	unsigned int round;
+	int fd;
+	size_t i;
+
+	if (!data || !buf)
+		fail("malloc");
+	fd = peer_socket(opts, &peer);
+	fill_payload(data, MAX_DGRAM + 1, 0);
+	expect_error(sendto(fd, data, MAX_DGRAM + 1, MSG_NOSIGNAL,
+			    (struct sockaddr *)&peer, sizeof(peer)),
+		     EMSGSIZE, "oversized datagram");
+	if (queue_bytes(fd, SIOCOUTQ))
+		fail("oversized datagram left pending output");
+	/* More than a default RX ring's worth, including on small rings. */
+	for (round = 0; round < 32; round++) {
+		for (i = 0; i < ARRAY_SIZE(sizes); i++) {
+			fill_payload(data, sizes[i], round * ARRAY_SIZE(sizes) + i);
+			if (opts->mode == TEST_MODE_CLIENT)
+				send_packet(fd, &peer, data, sizes[i]);
+			src = recv_packet(fd, buf, MAX_DGRAM + 1, 0, sizes[i], false);
+			check_source(opts, &peer, &src);
+			if (memcmp(data, buf, sizes[i]))
+				fail("payload mismatch: size %zu, round %u", sizes[i], round);
+			if (opts->mode == TEST_MODE_SERVER)
+				send_packet(fd, &src, buf, sizes[i]);
+		}
+	}
+	wait_sent(fd);
+	expect_empty(fd);
+	close(fd);
+	free(buf);
+	free(data);
+}
+
+void test_dgram_routes(const struct test_opts *opts)
+{
+	struct sockaddr_vm peer, src, stored, local;
+	int fd = peer_socket(opts, &peer);
+	int aux = bound_socket(VMADDR_CID_ANY);
+	unsigned int round;
+	char byte;
+
+	connect_fd(fd, &peer);
+	stored = sock_name(fd, true);
+	local = local_address(aux);
+	for (round = 0; round < 32; round++) {
+		send_packet(fd, &local, "L", 1);
+		recv_packet(aux, &byte, 1, 0, 1, false);
+		if (byte != 'L')
+			fail("sendto on a connected socket did not use loopback");
+		src = sock_name(fd, true);
+		if (src.svm_cid != stored.svm_cid || src.svm_port != stored.svm_port ||
+		    src.svm_flags != stored.svm_flags)
+			fail("per-message route changed the connected peer");
+		send_packet(fd, NULL, "P", 1);
+		src = recv_packet(fd, &byte, 1, 0, 1, false);
+		check_source(opts, &peer, &src);
+		if (byte != 'P')
+			fail("connected send did not retain the peer route");
+	}
+	wait_sent(fd);
+	close(aux);
+	close(fd);
+}
+
+void test_dgram_peer_filter(const struct test_opts *opts)
+{
+	struct sockaddr_vm peer, src;
+	int fd = peer_socket(opts, &peer);
+	int stray = bound_socket(opts->bind_cid);
+	char byte;
+
+	connect_fd(fd, &peer);
+	control_writeln("CONNECTED");
+	control_expectln("CONNECTED");
+	send_packet(stray, &peer, "B", 1);
+	send_packet(fd, NULL, "A", 1);
+	src = recv_packet(fd, &byte, 1, 0, 1, false);
+	check_source(opts, &peer, &src);
+	if (byte != 'A')
+		fail("connected socket accepted a different peer port");
+	disconnect_fd(fd);
+	control_writeln("DISCONNECTED");
+	control_expectln("DISCONNECTED");
+	send_packet(stray, &peer, "B", 1);
+	src = recv_packet(fd, &byte, 1, 0, 1, false);
+	if (byte != 'B' || src.svm_cid != peer.svm_cid || src.svm_port == peer.svm_port)
+		fail("disconnected socket did not accept the new peer");
+	close(stray);
+	close(fd);
+}
diff --git a/tools/testing/vsock/vsock_test_dgram.h b/tools/testing/vsock/vsock_test_dgram.h
new file mode 100644
index 00000000000000..0dec74ef5f4685
--- /dev/null
+++ b/tools/testing/vsock/vsock_test_dgram.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef VSOCK_TEST_DGRAM_H
+#define VSOCK_TEST_DGRAM_H
+
+#include "util.h"
+
+const char *vsock_dgram_check_local(const struct test_opts *opts);
+const char *vsock_dgram_check_peer(const struct test_opts *opts);
+const char *vsock_dgram_check_routes(const struct test_opts *opts);
+const char *vsock_dgram_check_vmci(const struct test_opts *opts);
+
+void test_dgram_bind(const struct test_opts *opts);
+void test_dgram_receive(const struct test_opts *opts);
+void test_dgram_receive_flags(const struct test_opts *opts);
+void test_dgram_filter(const struct test_opts *opts);
+void test_dgram_connect(const struct test_opts *opts);
+void test_dgram_source_cid(const struct test_opts *opts);
+void test_dgram_bound_lookup(const struct test_opts *opts);
+void test_dgram_sockmap(const struct test_opts *opts);
+void test_dgram_nested_source(const struct test_opts *opts);
+void test_dgram_sizes(const struct test_opts *opts);
+void test_dgram_routes(const struct test_opts *opts);
+void test_dgram_peer_filter(const struct test_opts *opts);
+void test_dgram_vmci_inq(const struct test_opts *opts);
+
+#endif /* VSOCK_TEST_DGRAM_H */

17/3 2026/10/29 04:42 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 32726
							output: 20
							thoughts: 1116
						

Thoughts:
**KMSAN Evaluation for Virtio-VSOCK SOCK_DGRAM Support**

Alright, let's break this down. The task is to determine if this patch series, implementing `SOCK_DGRAM` support for virtio-vsock/vhost-vsock, warrants a dedicated KMSAN fuzzing session. The standard KASAN kernel builds already cover a lot of ground: OOB, UAF, double frees, invalid frees, LOCKDEP, UB sanitizers, and general memory corruption. KMSAN's niche is detecting reads of *uninitialized* memory, particularly in kernel-to-user info-leaks. The rule is clear: if KASAN or other standard detectors would catch the bug, there is no need for KMSAN. KMSAN is expensive, so it should only be used if we're dealing with risks specific to uninitialized memory.

The criteria for *needing* KMSAN boils down to these scenarios:

1.  Kernel structures passed to userspace where fields, or padding, might be uninitialized.
2.  Conditional logic or branching dependent on uninitialized variables.
3.  Allocation and initialization of complex data structures with potentially uninitialized fields that *could* be read later.
4.  Bounds checks, lengths, or logic that allows access to uninitialized bytes of existing buffers.

Conversely, KMSAN isn't needed if:

*   The primary risks are out-of-bounds access, overflows, NULL dereferences, or use-after-free – things KASAN already catches.
*   All stack/heap structures are fully zeroed/initialized before being read or copied.
*   The patch doesn't introduce any risk of uninitialized memory usage or leaks.

Now, let's look at the patch. We have user-space interaction via `ioctl` and `virtio_transport_dgram_dequeue`. The `ioctl` looks safe; `n_bytes` is an initialized integer, passed through. The more interesting part is in `virtio_transport_dgram_dequeue`, specifically the handling of the `struct sockaddr_vm` which is initialized using `vsock_addr_init`. The critical question: Does `vsock_addr_init` fully initialize *all* the fields of `struct sockaddr_vm`, including any padding? If it doesn't, we're potentially sending uninitialized memory to userspace through `msg->msg_name`, triggering a KMSAN concern. I need to dive into the definition of `struct sockaddr_vm` and `vsock_addr_init` to confirm this. I will look at the code for these structures and functions now.



18/3 2026/10/29 04:43 tool grepper
0m Args:
{
  "Expression": "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/af_vsock.c=250=static int vsock_auto_bind(struct vsock_sock *vsk)\n--\nnet/vmw_vsock/af_vsock.c-256-\t\treturn 0;\nnet/vmw_vsock/af_vsock.c:257:\tvsock_addr_init(\u0026local_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);\nnet/vmw_vsock/af_vsock.c-258-\treturn __vsock_bind(sk, \u0026local_addr);\n--\nnet/vmw_vsock/af_vsock.c=947=static int __vsock_bind_common(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/af_vsock.c-955-\nnet/vmw_vsock/af_vsock.c:956:\tvsock_addr_init(\u0026new_addr, addr-\u003esvm_cid, addr-\u003esvm_port);\nnet/vmw_vsock/af_vsock.c-957-\n--\nnet/vmw_vsock/af_vsock.c-989-\nnet/vmw_vsock/af_vsock.c:990:\tvsock_addr_init(\u0026vsk-\u003elocal_addr, new_addr.svm_cid, new_addr.svm_port);\nnet/vmw_vsock/af_vsock.c-991-\n--\nnet/vmw_vsock/af_vsock.c=1061=static struct sock *__vsock_create(struct net *net,\n--\nnet/vmw_vsock/af_vsock.c-1085-\tvsk = vsock_sk(sk);\nnet/vmw_vsock/af_vsock.c:1086:\tvsock_addr_init(\u0026vsk-\u003elocal_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);\nnet/vmw_vsock/af_vsock.c:1087:\tvsock_addr_init(\u0026vsk-\u003eremote_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);\nnet/vmw_vsock/af_vsock.c-1088-\n--\nnet/vmw_vsock/af_vsock.c=1173=static void vsock_sk_destruct(struct sock *sk)\n--\nnet/vmw_vsock/af_vsock.c-1186-\t */\nnet/vmw_vsock/af_vsock.c:1187:\tvsock_addr_init(\u0026vsk-\u003elocal_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);\nnet/vmw_vsock/af_vsock.c:1188:\tvsock_addr_init(\u0026vsk-\u003eremote_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);\nnet/vmw_vsock/af_vsock.c-1189-\n--\nnet/vmw_vsock/af_vsock.c=1638=static int vsock_dgram_connect(struct socket *sock,\n--\nnet/vmw_vsock/af_vsock.c-1652-\t\tlock_sock(sk);\nnet/vmw_vsock/af_vsock.c:1653:\t\tvsock_addr_init(\u0026vsk-\u003eremote_addr, VMADDR_CID_ANY,\nnet/vmw_vsock/af_vsock.c-1654-\t\t\t\tVMADDR_PORT_ANY);\n--\nnet/vmw_vsock/hyperv_transport.c=170=static void hvs_addr_init(struct sockaddr_vm *addr, const guid_t *svr_id)\n--\nnet/vmw_vsock/hyperv_transport.c-173-\nnet/vmw_vsock/hyperv_transport.c:174:\tvsock_addr_init(addr, VMADDR_CID_ANY, port);\nnet/vmw_vsock/hyperv_transport.c-175-}\n--\nnet/vmw_vsock/hyperv_transport.c=295=static void hvs_open_connection(struct vmbus_channel *chan)\n--\nnet/vmw_vsock/hyperv_transport.c-338-\t\t/* Remote peer is always the host */\nnet/vmw_vsock/hyperv_transport.c:339:\t\tvsock_addr_init(\u0026vnew-\u003eremote_addr,\nnet/vmw_vsock/hyperv_transport.c-340-\t\t\t\tVMADDR_CID_HOST, VMADDR_PORT_ANY);\n--\nnet/vmw_vsock/virtio_transport_common.c=1039=virtio_transport_dgram_dequeue(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-1070-\nnet/vmw_vsock/virtio_transport_common.c:1071:\t\tvsock_addr_init(vm_addr, le64_to_cpu(hdr-\u003esrc_cid),\nnet/vmw_vsock/virtio_transport_common.c-1072-\t\t\t\tle32_to_cpu(hdr-\u003esrc_port));\n--\nnet/vmw_vsock/virtio_transport_common.c=1806=virtio_transport_recv_listen(struct sock *sk, struct sk_buff *skb,\n--\nnet/vmw_vsock/virtio_transport_common.c-1843-\tvchild = vsock_sk(child);\nnet/vmw_vsock/virtio_transport_common.c:1844:\tvsock_addr_init(\u0026vchild-\u003elocal_addr, le64_to_cpu(hdr-\u003edst_cid),\nnet/vmw_vsock/virtio_transport_common.c-1845-\t\t\tle32_to_cpu(hdr-\u003edst_port));\nnet/vmw_vsock/virtio_transport_common.c:1846:\tvsock_addr_init(\u0026vchild-\u003eremote_addr, le64_to_cpu(hdr-\u003esrc_cid),\nnet/vmw_vsock/virtio_transport_common.c-1847-\t\t\tle32_to_cpu(hdr-\u003esrc_port));\n--\nnet/vmw_vsock/virtio_transport_common.c=1910=void virtio_transport_recv_pkt(struct virtio_transport *t,\n--\nnet/vmw_vsock/virtio_transport_common.c-1918-\nnet/vmw_vsock/virtio_transport_common.c:1919:\tvsock_addr_init(\u0026src, le64_to_cpu(hdr-\u003esrc_cid),\nnet/vmw_vsock/virtio_transport_common.c-1920-\t\t\tle32_to_cpu(hdr-\u003esrc_port));\nnet/vmw_vsock/virtio_transport_common.c:1921:\tvsock_addr_init(\u0026dst, le64_to_cpu(hdr-\u003edst_cid),\nnet/vmw_vsock/virtio_transport_common.c-1922-\t\t\tle32_to_cpu(hdr-\u003edst_port));\n--\nnet/vmw_vsock/vmci_transport.c=176=vmci_transport_packet_get_addresses(struct vmci_transport_packet *pkt,\n--\nnet/vmw_vsock/vmci_transport.c-179-{\nnet/vmw_vsock/vmci_transport.c:180:\tvsock_addr_init(local, pkt-\u003edg.dst.context, pkt-\u003edst_port);\nnet/vmw_vsock/vmci_transport.c:181:\tvsock_addr_init(remote, pkt-\u003edg.src.context, pkt-\u003esrc_port);\nnet/vmw_vsock/vmci_transport.c-182-}\n--\nnet/vmw_vsock/vmci_transport.c=314=static int vmci_transport_send_reset(struct sock *sk,\n--\nnet/vmw_vsock/vmci_transport.c-331-\t} else {\nnet/vmw_vsock/vmci_transport.c:332:\t\tvsock_addr_init(\u0026dst, pkt-\u003edg.src.context,\nnet/vmw_vsock/vmci_transport.c-333-\t\t\t\tpkt-\u003esrc_port);\n--\nnet/vmw_vsock/vmci_transport.c=478=static struct sock *vmci_transport_get_pending(\n--\nnet/vmw_vsock/vmci_transport.c-486-\nnet/vmw_vsock/vmci_transport.c:487:\tvsock_addr_init(\u0026src, pkt-\u003edg.src.context, pkt-\u003esrc_port);\nnet/vmw_vsock/vmci_transport.c-488-\n--\nnet/vmw_vsock/vmci_transport.c=675=static int vmci_transport_recv_stream_cb(void *data, struct vmci_datagram *dg)\n--\nnet/vmw_vsock/vmci_transport.c-706-\t */\nnet/vmw_vsock/vmci_transport.c:707:\tvsock_addr_init(\u0026src, pkt-\u003edg.src.context, pkt-\u003esrc_port);\nnet/vmw_vsock/vmci_transport.c:708:\tvsock_addr_init(\u0026dst, pkt-\u003edg.dst.context, pkt-\u003edst_port);\nnet/vmw_vsock/vmci_transport.c-709-\n--\nnet/vmw_vsock/vmci_transport.c=913=static void vmci_transport_recv_pkt_work(struct work_struct *work)\n--\nnet/vmw_vsock/vmci_transport.c-925-\tlock_sock(sk);\nnet/vmw_vsock/vmci_transport.c:926:\tvsock_addr_init(\u0026src, pkt-\u003edg.src.context, pkt-\u003esrc_port);\nnet/vmw_vsock/vmci_transport.c-927-\tif (sk-\u003esk_state != TCP_LISTEN \u0026\u0026\n--\nnet/vmw_vsock/vmci_transport.c=970=static int vmci_transport_recv_listen(struct sock *sk,\n--\nnet/vmw_vsock/vmci_transport.c-1045-\nnet/vmw_vsock/vmci_transport.c:1046:\tvsock_addr_init(\u0026vpending-\u003elocal_addr, pkt-\u003edg.dst.context,\nnet/vmw_vsock/vmci_transport.c-1047-\t\t\tpkt-\u003edst_port);\nnet/vmw_vsock/vmci_transport.c:1048:\tvsock_addr_init(\u0026vpending-\u003eremote_addr, pkt-\u003edg.src.context,\nnet/vmw_vsock/vmci_transport.c-1049-\t\t\tpkt-\u003esrc_port);\n--\nnet/vmw_vsock/vmci_transport.c=1693=static int vmci_transport_dgram_bind(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/vmci_transport.c-1717-\t\treturn vmci_transport_error_to_vsock_error(err);\nnet/vmw_vsock/vmci_transport.c:1718:\tvsock_addr_init(\u0026vsk-\u003elocal_addr, addr-\u003esvm_cid,\nnet/vmw_vsock/vmci_transport.c-1719-\t\t\tvmci_trans(vsk)-\u003edg_handle.resource);\n--\nnet/vmw_vsock/vmci_transport.c=1773=static int vmci_transport_dgram_dequeue(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/vmci_transport.c-1815-\t\tDECLARE_SOCKADDR(struct sockaddr_vm *, vm_addr, msg-\u003emsg_name);\nnet/vmw_vsock/vmci_transport.c:1816:\t\tvsock_addr_init(vm_addr, dg-\u003esrc.context, dg-\u003esrc.resource);\nnet/vmw_vsock/vmci_transport.c-1817-\t\tmsg-\u003emsg_namelen = sizeof(*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--\nnet/vmw_vsock/vsock_addr.c-20-}\nnet/vmw_vsock/vsock_addr.c:21:EXPORT_SYMBOL_GPL(vsock_addr_init);\nnet/vmw_vsock/vsock_addr.c-22-\n--\nnet/vmw_vsock/vsock_addr.c=46=void vsock_addr_unbind(struct sockaddr_vm *addr)\nnet/vmw_vsock/vsock_addr.c-47-{\nnet/vmw_vsock/vsock_addr.c:48:\tvsock_addr_init(addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);\nnet/vmw_vsock/vsock_addr.c-49-}\n"
}

19/3 2026/10/29 04:43 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 37891
							output: 40
							thoughts: 38
						

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

**Diving into vsock_addr_init in net/vmw_vsock/vsock_addr.c**

Okay, I'm going to jump into the `net/vmw_vsock/vsock_addr.c` file, specifically around line 13. I want to understand exactly what the `vsock_addr_init` function is doing. I need to get a clear picture of how this component handles address initialization within the vsock subsystem. I'm hoping to quickly identify the key operations it performs – anything related to memory allocation, structure initialization, or potential default values being set. I'm looking for the core logic, the fundamental mechanics of how a vsock address gets set up.



20/3 2026/10/29 04:43 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/vsock_addr.c",
  "FirstLine": 1,
  "LineCount": 40
}

Results:
{
  "Contents": "   1:\t// SPDX-License-Identifier: GPL-2.0-only\n   2:\t/*\n   3:\t * VMware vSockets Driver\n   4:\t *\n   5:\t * Copyright (C) 2007-2012 VMware, Inc. All rights reserved.\n   6:\t */\n   7:\t\n   8:\t#include \u003clinux/types.h\u003e\n   9:\t#include \u003clinux/socket.h\u003e\n  10:\t#include \u003clinux/stddef.h\u003e\n  11:\t#include \u003cnet/sock.h\u003e\n  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  32:\t\n  33:\t\tif (addr-\u003esvm_flags \u0026 ~svm_valid_flags)\n  34:\t\t\treturn -EINVAL;\n  35:\t\n  36:\t\treturn 0;\n  37:\t}\n  38:\tEXPORT_SYMBOL_GPL(vsock_addr_validate);\n  39:\t\n  40:\tbool vsock_addr_bound(const struct sockaddr_vm *addr)\n"
}

21/3 2026/10/29 04:43 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 38670
							output: 42
							thoughts: 1046
						

Thoughts:
**Analyzing vsock Address Initialization and RX Path in Virtio-VSOCK**

Okay, here's what I'm seeing. First, I noticed `vsock_addr_init` does a `memset` to zero the entire `struct sockaddr_vm`. That's standard and expected, but it's important to remember this context when looking at how the `struct sockaddr_vm` is populated in the receiver path. I see that `virtio_transport_dgram_dequeue` initializes the destination address using this function.

Now, let's dive into the RX path. I'm focusing on the `virtio_vsock_rx_fill` function and how it sets up the `skb` (socket buffer) for receiving data. The allocation is done by `virtio_vsock_alloc_skb`, and the `skb` is then prepared for receive. `memset` is used on `skb->head`. A significant change involves preparing the receive buffers, now using `virtqueue_add_inbuf` after setting up scatter-gather lists. I need to understand this pattern of `skb_to_sgvec`, `virtqueue_add_inbuf` in `virtio_vsock_rx_fill` and `virtio_transport_rx_work` in relation to the virtio descriptor chaining.

I need to understand what happens to these zeroed buffers later, and how the received data gets written into them, and, crucially, how the virtio descriptors are set up and chained to describe the received data. I'll need to trace the data flow through `virtio_transport_rx_work` to understand how the received packets are processed.



22/3 2026/10/29 04:44 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/virtio_transport.c",
  "FirstLine": 300,
  "LineCount": 60
}

Results:
{
  "Contents": " 300:\t\t\tnew_cnt = atomic_sub_return(cnt, \u0026vsock-\u003equeued_replies);\n 301:\t\t\tif (new_cnt + cnt \u003e= virtqueue_get_vring_size(rx_vq) \u0026\u0026\n 302:\t\t\t    new_cnt \u003c virtqueue_get_vring_size(rx_vq))\n 303:\t\t\t\tqueue_work(virtio_vsock_workqueue, \u0026vsock-\u003erx_work);\n 304:\t\t}\n 305:\t\n 306:\t\tret = 0;\n 307:\t\n 308:\tout_rcu:\n 309:\t\trcu_read_unlock();\n 310:\t\treturn ret;\n 311:\t}\n 312:\t\n 313:\tstatic void virtio_vsock_rx_fill(struct virtio_vsock *vsock)\n 314:\t{\n 315:\t\tint total_len = VIRTIO_VSOCK_DEFAULT_RX_BUF_SIZE;\n 316:\t\tstruct virtqueue *vq;\n 317:\t\tstruct sk_buff *skb;\n 318:\t\tint ret;\n 319:\t\n 320:\t\tvq = vsock-\u003evqs[VSOCK_VQ_RX];\n 321:\t\t/* A datagram must fit in one RX buffer, including its header. */\n 322:\t\tif (vsock-\u003edgram_allow)\n 323:\t\t\ttotal_len = VIRTIO_VSOCK_SKB_HEADROOM +\n 324:\t\t\t\t    VIRTIO_VSOCK_MAX_PKT_BUF_SIZE;\n 325:\t\n 326:\t\tdo {\n 327:\t\t\tskb = virtio_vsock_alloc_skb(total_len, GFP_KERNEL);\n 328:\t\t\tif (!skb)\n 329:\t\t\t\tbreak;\n 330:\t\n 331:\t\t\tmemset(skb-\u003ehead, 0, VIRTIO_VSOCK_SKB_HEADROOM);\n 332:\t\t\t/* Post the header and the entire payload as one RX buffer. */\n 333:\t\t\tvirtio_vsock_skb_put(skb, total_len - VIRTIO_VSOCK_SKB_HEADROOM);\n 334:\t\t\tskb_push(skb, VIRTIO_VSOCK_SKB_HEADROOM);\n 335:\t\t\tsg_init_table(vsock-\u003ein_bufs, ARRAY_SIZE(vsock-\u003ein_bufs));\n 336:\t\t\tret = skb_to_sgvec(skb, vsock-\u003ein_bufs, 0, total_len);\n 337:\t\t\tskb_pull(skb, VIRTIO_VSOCK_SKB_HEADROOM);\n 338:\t\t\tif (ret \u003e= 0)\n 339:\t\t\t\tret = virtqueue_add_inbuf(vq, vsock-\u003ein_bufs, ret, skb,\n 340:\t\t\t\t\t\t\t  GFP_KERNEL);\n 341:\t\t\tif (ret \u003c 0) {\n 342:\t\t\t\tkfree_skb(skb);\n 343:\t\t\t\tbreak;\n 344:\t\t\t}\n 345:\t\n 346:\t\t\tvsock-\u003erx_buf_nr++;\n 347:\t\t} while (vq-\u003enum_free);\n 348:\t\tif (vsock-\u003erx_buf_nr \u003e vsock-\u003erx_buf_max_nr)\n 349:\t\t\tvsock-\u003erx_buf_max_nr = vsock-\u003erx_buf_nr;\n 350:\t\tvirtqueue_kick(vq);\n 351:\t}\n 352:\t\n 353:\tstatic void virtio_transport_tx_work(struct work_struct *work)\n 354:\t{\n 355:\t\tstruct virtio_vsock *vsock =\n 356:\t\t\tcontainer_of(work, struct virtio_vsock, tx_work);\n 357:\t\tstruct virtqueue *vq;\n 358:\t\tbool added = false;\n 359:\t\n"
}

23/3 2026/10/29 04:44 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 41164
							output: 42
							thoughts: 32
						

Thoughts:
Okay, here's my interpretation of that thought process, assuming I'm already deep in networking and kernel internals:

**Diving Deeper into the vsock Virtio Transport**

Right, so we've been examining the virtio-vsock implementation and specifically how data is received. I've got a good handle on some areas, but I need to solidify my understanding of the receive path. To do that properly, I'm going to turn my attention to `virtio_transport_rx_work` function, found in `net/vmw_vsock/virtio_transport.c`. This seems like the logical next step. I'm guessing this function is central to handling incoming data from the virtual socket. I need to understand how the virtio device interacts with the network stack, how the data is buffered, and where it ultimately ends up within the application or guest OS. Time to dissect that function and see what secrets it holds. I suspect this will offer crucial insights.



24/3 2026/10/29 04:44 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/virtio_transport.c",
  "FirstLine": 670,
  "LineCount": 70
}

Results:
{
  "Contents": " 670:\t\trcu_read_unlock();\n 671:\t\n 672:\t\treturn dgram_allow;\n 673:\t}\n 674:\t\n 675:\tstatic void virtio_transport_rx_work(struct work_struct *work)\n 676:\t{\n 677:\t\tstruct virtio_vsock *vsock =\n 678:\t\t\tcontainer_of(work, struct virtio_vsock, rx_work);\n 679:\t\tstruct virtqueue *vq;\n 680:\t\n 681:\t\tmutex_lock(\u0026vsock-\u003erx_lock);\n 682:\t\n 683:\t\tif (!vsock-\u003erx_run)\n 684:\t\t\tgoto out_nofill;\n 685:\t\n 686:\t\tvq = vsock-\u003evqs[VSOCK_VQ_RX];\n 687:\t\n 688:\t\tdo {\n 689:\t\t\tvirtqueue_disable_cb(vq);\n 690:\t\t\tfor (;;) {\n 691:\t\t\t\tunsigned int len, payload_len;\n 692:\t\t\t\tstruct virtio_vsock_hdr *hdr;\n 693:\t\t\t\tstruct sk_buff *skb;\n 694:\t\n 695:\t\t\t\tif (!virtio_transport_more_replies(vsock)) {\n 696:\t\t\t\t\t/* Stop rx until the device processes already\n 697:\t\t\t\t\t * pending replies.  Leave rx virtqueue\n 698:\t\t\t\t\t * callbacks disabled.\n 699:\t\t\t\t\t */\n 700:\t\t\t\t\tgoto out;\n 701:\t\t\t\t}\n 702:\t\n 703:\t\t\t\tskb = virtqueue_get_buf(vq, \u0026len);\n 704:\t\t\t\tif (!skb)\n 705:\t\t\t\t\tbreak;\n 706:\t\n 707:\t\t\t\tvsock-\u003erx_buf_nr--;\n 708:\t\n 709:\t\t\t\t/* Drop short/long packets */\n 710:\t\t\t\tif (unlikely(len \u003c sizeof(*hdr) ||\n 711:\t\t\t\t\t     len \u003e sizeof(*hdr) + skb-\u003elen)) {\n 712:\t\t\t\t\tkfree_skb(skb);\n 713:\t\t\t\t\tcontinue;\n 714:\t\t\t\t}\n 715:\t\n 716:\t\t\t\thdr = virtio_vsock_hdr(skb);\n 717:\t\t\t\tpayload_len = le32_to_cpu(hdr-\u003elen);\n 718:\t\t\t\tif (unlikely(payload_len \u003e len - sizeof(*hdr))) {\n 719:\t\t\t\t\tkfree_skb(skb);\n 720:\t\t\t\t\tcontinue;\n 721:\t\t\t\t}\n 722:\t\n 723:\t\t\t\tpskb_trim_unique(skb, payload_len);\n 724:\t\n 725:\t\t\t\tvirtio_transport_deliver_tap_pkt(skb);\n 726:\t\n 727:\t\t\t\t/* Force virtio-transport into global mode since it\n 728:\t\t\t\t * does not yet support local-mode namespacing.\n 729:\t\t\t\t */\n 730:\t\t\t\tvirtio_transport_recv_pkt(\u0026virtio_transport, skb, NULL);\n 731:\t\t\t}\n 732:\t\t} while (!virtqueue_enable_cb(vq));\n 733:\t\n 734:\tout:\n 735:\t\tif (!vsock-\u003erx_buf_nr ||\n 736:\t\t    vsock-\u003erx_buf_nr \u003c vsock-\u003erx_buf_max_nr / 2)\n 737:\t\t\tvirtio_vsock_rx_fill(vsock);\n 738:\tout_nofill:\n 739:\t\tmutex_unlock(\u0026vsock-\u003erx_lock);\n"
}

25/3 2026/10/29 04:44 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 42423
							output: 22
							thoughts: 140
						

Thoughts:
Here's my thought process, as if I were reviewing this code:

**Diving into virtio_vsock_rx_fill**

Okay, let's dissect what's happening in `virtio_vsock_rx_fill`. I see the code is using `virtio_vsock_alloc_linear_skb` to grab an SKB and set it up. Looking at this `virtio_vsock_alloc_linear_skb` versus `virtio_vsock_alloc_skb`, I can see a differentiation. I wonder what the key difference is between the two allocation methods? A quick search of the codebase for those two symbols should shed some light on the design decisions here, specifically why they have chosen this allocation method at this point in the process.



26/3 2026/10/29 04:44 tool grepper
0m Args:
{
  "Expression": "virtio_vsock_alloc_"
}

Results:
{
  "Output": "drivers/vhost/vsock.c=370=vhost_vsock_alloc_skb(struct vhost_virtqueue *vq,\n--\ndrivers/vhost/vsock.c-391-\t/* len contains both payload and hdr */\ndrivers/vhost/vsock.c:392:\tskb = virtio_vsock_alloc_skb(len, GFP_KERNEL);\ndrivers/vhost/vsock.c-393-\tif (!skb)\n--\ninclude/linux/virtio_vsock.h=63=static inline struct sk_buff *\ninclude/linux/virtio_vsock.h:64:__virtio_vsock_alloc_skb_with_frags(unsigned int header_len,\ninclude/linux/virtio_vsock.h-65-\t\t\t\t    unsigned int data_len,\n--\ninclude/linux/virtio_vsock.h=81=static inline struct sk_buff *\ninclude/linux/virtio_vsock.h:82:virtio_vsock_alloc_linear_skb(unsigned int size, gfp_t mask)\ninclude/linux/virtio_vsock.h-83-{\ninclude/linux/virtio_vsock.h:84:\treturn __virtio_vsock_alloc_skb_with_frags(size, 0, mask);\ninclude/linux/virtio_vsock.h-85-}\ninclude/linux/virtio_vsock.h-86-\ninclude/linux/virtio_vsock.h:87:static inline struct sk_buff *virtio_vsock_alloc_skb(unsigned int size, gfp_t mask)\ninclude/linux/virtio_vsock.h-88-{\ninclude/linux/virtio_vsock.h-89-\tif (size \u003c= SKB_WITH_OVERHEAD(PAGE_SIZE \u003c\u003c PAGE_ALLOC_COSTLY_ORDER))\ninclude/linux/virtio_vsock.h:90:\t\treturn virtio_vsock_alloc_linear_skb(size, mask);\ninclude/linux/virtio_vsock.h-91-\ninclude/linux/virtio_vsock.h-92-\tsize -= VIRTIO_VSOCK_SKB_HEADROOM;\ninclude/linux/virtio_vsock.h:93:\treturn __virtio_vsock_alloc_skb_with_frags(VIRTIO_VSOCK_SKB_HEADROOM,\ninclude/linux/virtio_vsock.h-94-\t\t\t\t\t\t   size, mask);\n--\nnet/vmw_vsock/virtio_transport.c=313=static void virtio_vsock_rx_fill(struct virtio_vsock *vsock)\n--\nnet/vmw_vsock/virtio_transport.c-326-\tdo {\nnet/vmw_vsock/virtio_transport.c:327:\t\tskb = virtio_vsock_alloc_skb(total_len, GFP_KERNEL);\nnet/vmw_vsock/virtio_transport.c-328-\t\tif (!skb)\n--\nnet/vmw_vsock/virtio_transport_common.c-29-/* Max payload that can be collapsed into a single linear skb, using the same\nnet/vmw_vsock/virtio_transport_common.c:30: * allocation threshold as virtio_vsock_alloc_skb() to avoid adding pressure\nnet/vmw_vsock/virtio_transport_common.c-31- * on the page allocator.\n--\nnet/vmw_vsock/virtio_transport_common.c=220=static struct sk_buff *virtio_transport_alloc_skb(struct virtio_vsock_pkt_info *info,\n--\nnet/vmw_vsock/virtio_transport_common.c-237-\nnet/vmw_vsock/virtio_transport_common.c:238:\tskb = virtio_vsock_alloc_skb(skb_len, GFP_KERNEL);\nnet/vmw_vsock/virtio_transport_common.c-239-\tif (!skb)\n--\nnet/vmw_vsock/virtio_transport_common.c=518=virtio_transport_collapse_rx_queue(struct virtio_vsock_sock *vvs,\n--\nnet/vmw_vsock/virtio_transport_common.c-574-\nnet/vmw_vsock/virtio_transport_common.c:575:\t\t\tnew_skb = virtio_vsock_alloc_linear_skb(alloc_size +\nnet/vmw_vsock/virtio_transport_common.c-576-\t\t\t\t\tVIRTIO_VSOCK_SKB_HEADROOM, GFP_KERNEL);\n"
}

27/3 2026/10/29 04:44 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 43932
							output: 38
							thoughts: 45
						

Thoughts:
Okay, let's dive into this.

**Investigating the Virtio-VSOCK SKB Allocation**

Right, my focus is on `__virtio_vsock_alloc_skb_with_frags`. I need to understand how this is handled. To get a handle on it, I'll need to reference the header file. Specifically, I'll be looking around line 60 of `include/linux/virtio_vsock.h`. That's where I expect to find the relevant data structures or function prototypes related to this SKB allocation with fragments. I'm guessing that's where the core mechanisms will be defined, or at least declared. Time to see what's what.



28/3 2026/10/29 04:44 tool read-file
0m Args:
{
  "File": "include/linux/virtio_vsock.h",
  "FirstLine": 55,
  "LineCount": 45
}

Results:
{
  "Contents": "  55:\t\tDEBUG_NET_WARN_ON_ONCE(skb-\u003elen);\n  56:\t\n  57:\t\tif (skb_is_nonlinear(skb))\n  58:\t\t\tskb-\u003elen = len;\n  59:\t\telse\n  60:\t\t\tskb_put(skb, len);\n  61:\t}\n  62:\t\n  63:\tstatic inline struct sk_buff *\n  64:\t__virtio_vsock_alloc_skb_with_frags(unsigned int header_len,\n  65:\t\t\t\t\t    unsigned int data_len,\n  66:\t\t\t\t\t    gfp_t mask)\n  67:\t{\n  68:\t\tstruct sk_buff *skb;\n  69:\t\tint err;\n  70:\t\n  71:\t\tskb = alloc_skb_with_frags(header_len, data_len,\n  72:\t\t\t\t\t   PAGE_ALLOC_COSTLY_ORDER, \u0026err, mask);\n  73:\t\tif (!skb)\n  74:\t\t\treturn NULL;\n  75:\t\n  76:\t\tskb_reserve(skb, VIRTIO_VSOCK_SKB_HEADROOM);\n  77:\t\tskb-\u003edata_len = data_len;\n  78:\t\treturn skb;\n  79:\t}\n  80:\t\n  81:\tstatic inline struct sk_buff *\n  82:\tvirtio_vsock_alloc_linear_skb(unsigned int size, gfp_t mask)\n  83:\t{\n  84:\t\treturn __virtio_vsock_alloc_skb_with_frags(size, 0, mask);\n  85:\t}\n  86:\t\n  87:\tstatic inline struct sk_buff *virtio_vsock_alloc_skb(unsigned int size, gfp_t mask)\n  88:\t{\n  89:\t\tif (size \u003c= SKB_WITH_OVERHEAD(PAGE_SIZE \u003c\u003c PAGE_ALLOC_COSTLY_ORDER))\n  90:\t\t\treturn virtio_vsock_alloc_linear_skb(size, mask);\n  91:\t\n  92:\t\tsize -= VIRTIO_VSOCK_SKB_HEADROOM;\n  93:\t\treturn __virtio_vsock_alloc_skb_with_frags(VIRTIO_VSOCK_SKB_HEADROOM,\n  94:\t\t\t\t\t\t\t   size, mask);\n  95:\t}\n  96:\t\n  97:\tstatic inline void\n  98:\tvirtio_vsock_skb_queue_head(struct sk_buff_head *list, struct sk_buff *skb)\n  99:\t{\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 44850
							output: 25
							thoughts: 154
						

Thoughts:
**Analyzing `virtio_vsock_skb_cb` Initialization**

Okay, let's dive into this `virtio_vsock_skb_cb` structure. I see it defined in `include/linux/virtio_vsock.h`. Hmm, the recent addition of `tx_accounted` and `to_host` flags is interesting, and the comment about `bytes_unsent` suggests some careful accounting is involved, particularly around datagram payloads. I need to understand how this struct gets initialized and used.

My immediate next step is to track down where this structure, or the macro `VIRTIO_VSOCK_SKB_CB`, is actually set up in the code. I'll do a quick search; a straightforward search for either should give me the relevant initialization sites. Knowing where it's initialized is key to understanding its lifecycle and how these flags are managed. I'm expecting to find it either allocated with `kmalloc` or possibly embedded within another larger structure, but the details are what I need to see now.



30/3 2026/10/29 04:44 tool grepper
0m Args:
{
  "Expression": "VIRTIO_VSOCK_SKB_CB"
}

Results:
{
  "Output": "drivers/vhost/vsock.c=110=vhost_transport_do_send_pkt(struct vhost_vsock *vsock,\n--\ndrivers/vhost/vsock.c-179-\t\tiov_iter_init(\u0026iov_iter, ITER_DEST, \u0026vq-\u003eiov[out], in, iov_len);\ndrivers/vhost/vsock.c:180:\t\toffset = VIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset;\ndrivers/vhost/vsock.c-181-\t\tpayload_len = skb-\u003elen - offset;\n--\ndrivers/vhost/vsock.c-246-\ndrivers/vhost/vsock.c:247:\t\tVIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset += payload_len;\ndrivers/vhost/vsock.c-248-\t\ttotal_len += payload_len;\n--\ndrivers/vhost/vsock.c-252-\t\t */\ndrivers/vhost/vsock.c:253:\t\tif (VIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset \u003c skb-\u003elen) {\ndrivers/vhost/vsock.c-254-\t\t\thdr-\u003eflags |= cpu_to_le32(flags_to_restore);\n--\ninclude/linux/virtio_vsock.h=12=struct virtio_vsock_skb_cb {\n--\ninclude/linux/virtio_vsock.h-20-\ninclude/linux/virtio_vsock.h:21:#define VIRTIO_VSOCK_SKB_CB(skb) ((struct virtio_vsock_skb_cb *)((skb)-\u003ecb))\ninclude/linux/virtio_vsock.h-22-\n--\ninclude/linux/virtio_vsock.h=28=static inline bool virtio_vsock_skb_reply(struct sk_buff *skb)\ninclude/linux/virtio_vsock.h-29-{\ninclude/linux/virtio_vsock.h:30:\treturn VIRTIO_VSOCK_SKB_CB(skb)-\u003ereply;\ninclude/linux/virtio_vsock.h-31-}\n--\ninclude/linux/virtio_vsock.h=33=static inline void virtio_vsock_skb_set_reply(struct sk_buff *skb)\ninclude/linux/virtio_vsock.h-34-{\ninclude/linux/virtio_vsock.h:35:\tVIRTIO_VSOCK_SKB_CB(skb)-\u003ereply = true;\ninclude/linux/virtio_vsock.h-36-}\n--\ninclude/linux/virtio_vsock.h=38=static inline bool virtio_vsock_skb_tap_delivered(struct sk_buff *skb)\ninclude/linux/virtio_vsock.h-39-{\ninclude/linux/virtio_vsock.h:40:\treturn VIRTIO_VSOCK_SKB_CB(skb)-\u003etap_delivered;\ninclude/linux/virtio_vsock.h-41-}\n--\ninclude/linux/virtio_vsock.h=43=static inline void virtio_vsock_skb_set_tap_delivered(struct sk_buff *skb)\ninclude/linux/virtio_vsock.h-44-{\ninclude/linux/virtio_vsock.h:45:\tVIRTIO_VSOCK_SKB_CB(skb)-\u003etap_delivered = true;\ninclude/linux/virtio_vsock.h-46-}\n--\ninclude/linux/virtio_vsock.h=48=static inline void virtio_vsock_skb_clear_tap_delivered(struct sk_buff *skb)\ninclude/linux/virtio_vsock.h-49-{\ninclude/linux/virtio_vsock.h:50:\tVIRTIO_VSOCK_SKB_CB(skb)-\u003etap_delivered = false;\ninclude/linux/virtio_vsock.h-51-}\n--\nnet/vmw_vsock/virtio_transport_common.c=119=static struct sk_buff *virtio_transport_build_skb(void *opaque)\n--\nnet/vmw_vsock/virtio_transport_common.c-182-\nnet/vmw_vsock/virtio_transport_common.c:183:\t\tif (skb_copy_datagram_iter(pkt, VIRTIO_VSOCK_SKB_CB(pkt)-\u003eoffset,\nnet/vmw_vsock/virtio_transport_common.c-184-\t\t\t\t\t   \u0026iov_iter, payload_len)) {\n--\nnet/vmw_vsock/virtio_transport_common.c=307=static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-426-\t\t\tspin_unlock_bh(\u0026vvs-\u003etx_lock);\nnet/vmw_vsock/virtio_transport_common.c:427:\t\t\tVIRTIO_VSOCK_SKB_CB(skb)-\u003etx_accounted = true;\nnet/vmw_vsock/virtio_transport_common.c-428-\t\t\tskb-\u003edestructor = virtio_transport_dgram_skb_destructor;\n--\nnet/vmw_vsock/virtio_transport_common.c=476=static bool virtio_transport_can_collapse(struct sk_buff *skb)\n--\nnet/vmw_vsock/virtio_transport_common.c-482-\t */\nnet/vmw_vsock/virtio_transport_common.c:483:\tif (VIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset)\nnet/vmw_vsock/virtio_transport_common.c-484-\t\treturn false;\n--\nnet/vmw_vsock/virtio_transport_common.c=496=virtio_transport_collapse_size(struct sk_buff *skb, struct sk_buff_head *queue)\nnet/vmw_vsock/virtio_transport_common.c-497-{\nnet/vmw_vsock/virtio_transport_common.c:498:\tunsigned int target = skb-\u003elen - VIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset;\nnet/vmw_vsock/virtio_transport_common.c-499-\n--\nnet/vmw_vsock/virtio_transport_common.c-501-\t       virtio_transport_can_collapse(skb)) {\nnet/vmw_vsock/virtio_transport_common.c:502:\t\tunsigned int len = skb-\u003elen - VIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset;\nnet/vmw_vsock/virtio_transport_common.c-503-\n--\nnet/vmw_vsock/virtio_transport_common.c=518=virtio_transport_collapse_rx_queue(struct virtio_vsock_sock *vvs,\n--\nnet/vmw_vsock/virtio_transport_common.c-528-\t\tstruct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);\nnet/vmw_vsock/virtio_transport_common.c:529:\t\tu32 src_off = VIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset;\nnet/vmw_vsock/virtio_transport_common.c-530-\t\tu32 src_len = skb-\u003elen - src_off;\n--\nnet/vmw_vsock/virtio_transport_common.c=655=void virtio_transport_consume_skb_sent(struct sk_buff *skb, bool consume)\n--\nnet/vmw_vsock/virtio_transport_common.c-660-\t    (s-\u003esk_type != SOCK_DGRAM ||\nnet/vmw_vsock/virtio_transport_common.c:661:\t     VIRTIO_VSOCK_SKB_CB(skb)-\u003etx_accounted)) {\nnet/vmw_vsock/virtio_transport_common.c-662-\t\tstruct vsock_sock *vs = vsock_sk(s);\n--\nnet/vmw_vsock/virtio_transport_common.c-672-\tif (s \u0026\u0026 s-\u003esk_type == SOCK_DGRAM)\nnet/vmw_vsock/virtio_transport_common.c:673:\t\tVIRTIO_VSOCK_SKB_CB(skb)-\u003etx_accounted = false;\nnet/vmw_vsock/virtio_transport_common.c-674-\n--\nnet/vmw_vsock/virtio_transport_common.c=721=virtio_transport_stream_do_peek(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-735-\t\tbytes = min_t(size_t, len - total,\nnet/vmw_vsock/virtio_transport_common.c:736:\t\t\t      skb-\u003elen - VIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset);\nnet/vmw_vsock/virtio_transport_common.c-737-\n--\nnet/vmw_vsock/virtio_transport_common.c-742-\t\t */\nnet/vmw_vsock/virtio_transport_common.c:743:\t\terr = skb_copy_datagram_iter(skb, VIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset,\nnet/vmw_vsock/virtio_transport_common.c-744-\t\t\t\t\t     \u0026msg-\u003emsg_iter, bytes);\n--\nnet/vmw_vsock/virtio_transport_common.c=767=virtio_transport_stream_do_dequeue(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-792-\t\tbytes = min_t(size_t, len - total,\nnet/vmw_vsock/virtio_transport_common.c:793:\t\t\t      skb-\u003elen - VIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset);\nnet/vmw_vsock/virtio_transport_common.c-794-\n--\nnet/vmw_vsock/virtio_transport_common.c-800-\t\terr = skb_copy_datagram_iter(skb,\nnet/vmw_vsock/virtio_transport_common.c:801:\t\t\t\t\t     VIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset,\nnet/vmw_vsock/virtio_transport_common.c-802-\t\t\t\t\t     \u0026msg-\u003emsg_iter, bytes);\n--\nnet/vmw_vsock/virtio_transport_common.c-809-\nnet/vmw_vsock/virtio_transport_common.c:810:\t\tVIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset += bytes;\nnet/vmw_vsock/virtio_transport_common.c-811-\nnet/vmw_vsock/virtio_transport_common.c:812:\t\tif (skb-\u003elen == VIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset) {\nnet/vmw_vsock/virtio_transport_common.c-813-\t\t\tdequeued = le32_to_cpu(virtio_vsock_hdr(skb)-\u003elen);\n--\nnet/vmw_vsock/virtio_transport_common.c=850=virtio_transport_seqpacket_do_peek(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-882-\t\t\t */\nnet/vmw_vsock/virtio_transport_common.c:883:\t\t\terr = skb_copy_datagram_iter(skb, VIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset,\nnet/vmw_vsock/virtio_transport_common.c-884-\t\t\t\t\t\t     \u0026msg-\u003emsg_iter, bytes);\n--\nnet/vmw_vsock/virtio_transport_common.c=1039=virtio_transport_dgram_dequeue(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-1073-\t\tmsg-\u003emsg_namelen = sizeof(*vm_addr);\nnet/vmw_vsock/virtio_transport_common.c:1074:\t\tif (VIRTIO_VSOCK_SKB_CB(skb)-\u003eto_host)\nnet/vmw_vsock/virtio_transport_common.c-1075-\t\t\tvm_addr-\u003esvm_flags = VMADDR_FLAG_TO_HOST;\n--\nnet/vmw_vsock/virtio_transport_common.c=1910=void virtio_transport_recv_pkt(struct virtio_transport *t,\n--\nnet/vmw_vsock/virtio_transport_common.c-1962-\nnet/vmw_vsock/virtio_transport_common.c:1963:\t\tVIRTIO_VSOCK_SKB_CB(skb)-\u003eto_host =\nnet/vmw_vsock/virtio_transport_common.c-1964-\t\t\tt-\u003etransport.get_local_cid() \u003e VMADDR_CID_HOST;\n--\nnet/vmw_vsock/virtio_transport_common.c=2089=int virtio_transport_read_skb(struct vsock_sock *vsk, skb_read_actor_t recv_actor)\n--\nnet/vmw_vsock/virtio_transport_common.c-2114-\tpkt_len = le32_to_cpu(hdr-\u003elen);\nnet/vmw_vsock/virtio_transport_common.c:2115:\tbytes_read = skb-\u003elen - VIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset;\nnet/vmw_vsock/virtio_transport_common.c-2116-\tvirtio_transport_dec_rx_pkt(vvs, bytes_read, pkt_len);\n"
}

31/3 2026/10/29 04:44 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 48815
							output: 44
							thoughts: 73
						

Thoughts:
Here's my analysis of the provided snippet, framed as my own thought process:

**Diving into virtio-vsock skb Initialization**

Alright, let's get down to brass tacks. I need to understand how the `skb->cb` field is initialized when we allocate an `skb` in the virtio-vsock driver. This is a crucial area for performance and correct operation. My gut tells me there's something specific going on in the allocation process.

To get the full picture, I'll need to drill down into the code. Specifically, I should be looking at `virtio_transport_alloc_skb`. Based on the context, that's where the allocation is handled. I'm going to jump into lines 220 to 260 of `net/vmw_vsock/virtio_transport_common.c` to see how the code actually does it. This range should give me the core logic related to the allocation and, hopefully, the `skb->cb` initialization details I'm after. Let's see what gems are hidden in there.



32/3 2026/10/29 04:44 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/virtio_transport_common.c",
  "FirstLine": 220,
  "LineCount": 45
}

Results:
{
  "Contents": " 220:\tstatic struct sk_buff *virtio_transport_alloc_skb(struct virtio_vsock_pkt_info *info,\n 221:\t\t\t\t\t\t\t  size_t payload_len,\n 222:\t\t\t\t\t\t\t  bool zcopy,\n 223:\t\t\t\t\t\t\t  struct ubuf_info *uarg,\n 224:\t\t\t\t\t\t\t  u32 src_cid,\n 225:\t\t\t\t\t\t\t  u32 src_port,\n 226:\t\t\t\t\t\t\t  u32 dst_cid,\n 227:\t\t\t\t\t\t\t  u32 dst_port)\n 228:\t{\n 229:\t\tstruct vsock_sock *vsk;\n 230:\t\tstruct sk_buff *skb;\n 231:\t\tsize_t skb_len;\n 232:\t\n 233:\t\tskb_len = VIRTIO_VSOCK_SKB_HEADROOM;\n 234:\t\n 235:\t\tif (!zcopy)\n 236:\t\t\tskb_len += payload_len;\n 237:\t\n 238:\t\tskb = virtio_vsock_alloc_skb(skb_len, GFP_KERNEL);\n 239:\t\tif (!skb)\n 240:\t\t\treturn NULL;\n 241:\t\n 242:\t\tvirtio_transport_init_hdr(skb, info, payload_len, src_cid, src_port,\n 243:\t\t\t\t\t  dst_cid, dst_port);\n 244:\t\n 245:\t\tvsk = info-\u003evsk;\n 246:\t\n 247:\t\t/* If 'vsk' != NULL then payload is always present, so we\n 248:\t\t * will never call '__zerocopy_sg_from_iter()' below without\n 249:\t\t * setting skb owner in 'skb_set_owner_w()'. The only case\n 250:\t\t * when 'vsk' == NULL is VIRTIO_VSOCK_OP_RST control message\n 251:\t\t * without payload.\n 252:\t\t */\n 253:\t\tWARN_ON_ONCE(!(vsk \u0026\u0026 (info-\u003emsg \u0026\u0026 payload_len)) \u0026\u0026 zcopy);\n 254:\t\n 255:\t\t/* Set owner here, because '__zerocopy_sg_from_iter()' uses\n 256:\t\t * owner of skb without check to update 'sk_wmem_alloc'.\n 257:\t\t */\n 258:\t\tif (vsk)\n 259:\t\t\tskb_set_owner_w(skb, sk_vsock(vsk));\n 260:\t\n 261:\t\tif (info-\u003emsg \u0026\u0026 payload_len \u003e 0) {\n 262:\t\t\tint err;\n 263:\t\n 264:\t\t\t/* Bind the zerocopy lifetime before filling frags so error\n"
}

33/3 2026/10/29 04:44 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 49910
							output: 20
							thoughts: 118
						

Thoughts:
**Kernel Memory Allocation Concerns**

Okay, so I'm digging into how `alloc_skb_with_frags` and `alloc_skb` handle memory allocation within the kernel. Let's see... `alloc_skb` appears to utilize either `kmem_cache_alloc_node(skbuff_cache)` or the internal function `__alloc_skb`. Now, in the depths of `__alloc_skb`, I see a `memset` call: `memset(skb->cb, 0, sizeof(skb->cb));`. Hmm, interesting. This raises a question: does `__alloc_skb` indeed initialize the control buffer, `skb->cb`, to zero? I need to verify this assumption definitively. Best to go back and check the actual implementation of `__alloc_skb` itself!



34/3 2026/10/29 04:44 tool grepper
0m Args:
{
  "Expression": "memset\\(skb-\u003ecb"
}

Results:
{
  "Output": "\nFull output is too long, showing 200 out of 208 lines.\nUse more precise expression or PathPrefixes if possible.\n\nNumber of matching lines per file (29 files in total):\ndrivers/net/ethernet/mellanox/mlx5/core/en/ptp.c:2\ndrivers/net/ethernet/mellanox/mlxsw/spectrum.c:1\ndrivers/net/ovpn/tcp.c:1\ndrivers/net/usb/lan78xx.c:1\ndrivers/net/usb/usbnet.c:1\ndrivers/net/wireless/ath/wil6210/txrx.c:2\ndrivers/net/wireless/ath/wil6210/txrx_edma.c:1\ndrivers/net/wireless/intel/ipw2x00/ipw2200.c:1\ndrivers/net/wireless/intel/ipw2x00/libipw_rx.c:2\ndrivers/net/wireless/microchip/wilc1000/mon.c:1\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:2\ninclude/net/ieee802154_netdev.h:1\ninclude/net/ip6_tunnel.h:1\nnet/batman-adv/main.c:1\nnet/batman-adv/mesh-interface.c:1\nnet/bridge/br_device.c:1\nnet/bridge/br_input.c:1\nnet/bridge/netfilter/nf_conntrack_bridge.c:1\nnet/dsa/user.c:1\nnet/ipv4/tcp_output.c:1\nnet/ipv6/ip6_tunnel.c:1\nnet/l2tp/l2tp_core.c:1\nnet/mac80211/rx.c:1\nnet/mac80211/status.c:1\nnet/netfilter/nf_flow_table_ip.c:2\nnet/nfc/netlink.c:1\nnet/wireless/nl80211.c:2\nnet/wireless/util.c:1\nnet/xfrm/espintcp.c:1\n\ndrivers/net/ethernet/mellanox/mlx5/core/en/ptp.c=73=void mlx5e_skb_cb_hwtstamp_init(struct sk_buff *skb)\ndrivers/net/ethernet/mellanox/mlx5/core/en/ptp.c-74-{\ndrivers/net/ethernet/mellanox/mlx5/core/en/ptp.c:75:\tmemset(skb-\u003ecb, 0, sizeof(struct mlx5e_skb_cb_hwtstamp));\ndrivers/net/ethernet/mellanox/mlx5/core/en/ptp.c-76-}\n--\ndrivers/net/ethernet/mellanox/mlx5/core/en/ptp.c=113=void mlx5e_skb_cb_hwtstamp_handler(struct sk_buff *skb, int hwtstamp_type,\n--\ndrivers/net/ethernet/mellanox/mlx5/core/en/ptp.c-133-\tmlx5e_skb_cb_hwtstamp_tx(skb, ptpsq);\ndrivers/net/ethernet/mellanox/mlx5/core/en/ptp.c:134:\tmemset(skb-\u003ecb, 0, sizeof(struct mlx5e_skb_cb_hwtstamp));\ndrivers/net/ethernet/mellanox/mlx5/core/en/ptp.c-135-}\n--\ndrivers/net/ethernet/mellanox/mlxsw/spectrum.c=614=static netdev_tx_t mlxsw_sp_port_xmit(struct sk_buff *skb,\n--\ndrivers/net/ethernet/mellanox/mlxsw/spectrum.c-626-\ndrivers/net/ethernet/mellanox/mlxsw/spectrum.c:627:\tmemset(skb-\u003ecb, 0, sizeof(struct mlxsw_skb_cb));\ndrivers/net/ethernet/mellanox/mlxsw/spectrum.c-628-\n--\ndrivers/net/ovpn/tcp.c=64=static void ovpn_tcp_to_userspace(struct ovpn_peer *peer, struct sock *sk,\n--\ndrivers/net/ovpn/tcp.c-67-\tskb_set_owner_r(skb, sk);\ndrivers/net/ovpn/tcp.c:68:\tmemset(skb-\u003ecb, 0, sizeof(skb-\u003ecb));\ndrivers/net/ovpn/tcp.c-69-\tskb_queue_tail(\u0026peer-\u003etcp.user_queue, skb);\n--\ndrivers/net/usb/lan78xx.c=3895=static void lan78xx_skb_return(struct lan78xx_net *dev, struct sk_buff *skb)\n--\ndrivers/net/usb/lan78xx.c-3903-\t\t  skb-\u003elen + sizeof(struct ethhdr), skb-\u003eprotocol);\ndrivers/net/usb/lan78xx.c:3904:\tmemset(skb-\u003ecb, 0, sizeof(struct skb_data));\ndrivers/net/usb/lan78xx.c-3905-\n--\ndrivers/net/usb/usbnet.c=326=void usbnet_skb_return(struct usbnet *dev, struct sk_buff *skb)\n--\ndrivers/net/usb/usbnet.c-349-\t\t  skb-\u003elen + sizeof(struct ethhdr), skb-\u003eprotocol);\ndrivers/net/usb/usbnet.c:350:\tmemset(skb-\u003ecb, 0, sizeof(struct skb_data));\ndrivers/net/usb/usbnet.c-351-\n--\ndrivers/net/wireless/ath/wil6210/txrx.c=1725=static int __wil_tx_vring_tso(struct wil6210_priv *wil, struct wil6210_vif *vif,\n--\ndrivers/net/wireless/ath/wil6210/txrx.c-2002-\telse\ndrivers/net/wireless/ath/wil6210/txrx.c:2003:\t\tmemset(skb-\u003ecb, 0, sizeof(ktime_t));\ndrivers/net/wireless/ath/wil6210/txrx.c-2004-\n--\ndrivers/net/wireless/ath/wil6210/txrx.c=2026=static int __wil_tx_ring(struct wil6210_priv *wil, struct wil6210_vif *vif,\n--\ndrivers/net/wireless/ath/wil6210/txrx.c-2157-\telse\ndrivers/net/wireless/ath/wil6210/txrx.c:2158:\t\tmemset(skb-\u003ecb, 0, sizeof(ktime_t));\ndrivers/net/wireless/ath/wil6210/txrx.c-2159-\n--\ndrivers/net/wireless/ath/wil6210/txrx_edma.c=1400=static int __wil_tx_ring_tso_edma(struct wil6210_priv *wil,\n--\ndrivers/net/wireless/ath/wil6210/txrx_edma.c-1518-\telse\ndrivers/net/wireless/ath/wil6210/txrx_edma.c:1519:\t\tmemset(skb-\u003ecb, 0, sizeof(ktime_t));\ndrivers/net/wireless/ath/wil6210/txrx_edma.c-1520-\n--\ndrivers/net/wireless/intel/ipw2x00/ipw2200.c=8154=static void ipw_handle_mgmt_packet(struct ipw_priv *priv,\n--\ndrivers/net/wireless/intel/ipw2x00/ipw2200.c-8194-\t\tskb-\u003eprotocol = cpu_to_be16(ETH_P_80211_STATS);\ndrivers/net/wireless/intel/ipw2x00/ipw2200.c:8195:\t\tmemset(skb-\u003ecb, 0, sizeof(rxb-\u003eskb-\u003ecb));\ndrivers/net/wireless/intel/ipw2x00/ipw2200.c-8196-\t\tnetif_rx(skb);\n--\ndrivers/net/wireless/intel/ipw2x00/libipw_rx.c=32=static void libipw_monitor_rx(struct libipw_device *ieee,\n--\ndrivers/net/wireless/intel/ipw2x00/libipw_rx.c-43-\tskb-\u003eprotocol = htons(ETH_P_80211_RAW);\ndrivers/net/wireless/intel/ipw2x00/libipw_rx.c:44:\tmemset(skb-\u003ecb, 0, sizeof(skb-\u003ecb));\ndrivers/net/wireless/intel/ipw2x00/libipw_rx.c-45-\tnetif_rx(skb);\n--\ndrivers/net/wireless/intel/ipw2x00/libipw_rx.c=325=int libipw_rx(struct libipw_device *ieee, struct sk_buff *skb,\n--\ndrivers/net/wireless/intel/ipw2x00/libipw_rx.c-797-\t\tskb-\u003eprotocol = eth_type_trans(skb, dev);\ndrivers/net/wireless/intel/ipw2x00/libipw_rx.c:798:\t\tmemset(skb-\u003ecb, 0, sizeof(skb-\u003ecb));\ndrivers/net/wireless/intel/ipw2x00/libipw_rx.c-799-\t\tskb-\u003eip_summed = CHECKSUM_NONE;\t/* 802.11 crc not sufficient */\n--\ndrivers/net/wireless/microchip/wilc1000/mon.c=24=void wilc_wfi_monitor_rx(struct net_device *mon_dev, u8 *buff, u32 size)\n--\ndrivers/net/wireless/microchip/wilc1000/mon.c-92-\tskb-\u003eprotocol = htons(ETH_P_802_2);\ndrivers/net/wireless/microchip/wilc1000/mon.c:93:\tmemset(skb-\u003ecb, 0, sizeof(skb-\u003ecb));\ndrivers/net/wireless/microchip/wilc1000/mon.c-94-\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=1343=static void mac80211_hwsim_monitor_rx(struct ieee80211_hw *hw,\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1389-\tskb-\u003eprotocol = htons(ETH_P_802_2);\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:1390:\tmemset(skb-\u003ecb, 0, sizeof(skb-\u003ecb));\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1391-\tnetif_rx(skb);\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c=1395=static void mac80211_hwsim_monitor_ack(u32 freq, const u8 *addr)\n--\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1431-\tskb-\u003eprotocol = htons(ETH_P_802_2);\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:1432:\tmemset(skb-\u003ecb, 0, sizeof(skb-\u003ecb));\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c-1433-\tnetif_rx(skb);\n--\ninclude/net/ieee802154_netdev.h=402=static inline struct ieee802154_mac_cb *mac_cb_init(struct sk_buff *skb)\n--\ninclude/net/ieee802154_netdev.h-405-\ninclude/net/ieee802154_netdev.h:406:\tmemset(skb-\u003ecb, 0, sizeof(struct ieee802154_mac_cb));\ninclude/net/ieee802154_netdev.h-407-\treturn mac_cb(skb);\n--\ninclude/net/ip6_tunnel.h=155=static inline void ip6tunnel_xmit(struct sock *sk, struct sk_buff *skb,\n--\ninclude/net/ip6_tunnel.h-171-\ninclude/net/ip6_tunnel.h:172:\tmemset(skb-\u003ecb, 0, sizeof(struct inet6_skb_parm));\ninclude/net/ip6_tunnel.h-173-\tIP6CB(skb)-\u003eflags = ip6cb_flags;\n--\nnet/batman-adv/main.c=471=int batadv_batman_skb_recv(struct sk_buff *skb, struct net_device *dev,\n--\nnet/batman-adv/main.c-526-\t/* reset control block to avoid left overs from previous users */\nnet/batman-adv/main.c:527:\tmemset(skb-\u003ecb, 0, sizeof(struct batadv_skb_cb));\nnet/batman-adv/main.c-528-\n--\nnet/batman-adv/mesh-interface.c=213=static netdev_tx_t batadv_interface_tx(struct sk_buff *skb,\n--\nnet/batman-adv/mesh-interface.c-250-\t/* reset control block to avoid left overs from previous users */\nnet/batman-adv/mesh-interface.c:251:\tmemset(skb-\u003ecb, 0, sizeof(struct batadv_skb_cb));\nnet/batman-adv/mesh-interface.c-252-\n--\nnet/bridge/br_device.c=30=netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)\n--\nnet/bridge/br_device.c-48-\nnet/bridge/br_device.c:49:\tmemset(skb-\u003ecb, 0, sizeof(struct br_input_skb_cb));\nnet/bridge/br_device.c-50-\tbr_tc_skb_miss_set(skb, false);\n--\nnet/bridge/br_input.c=349=static rx_handler_result_t br_handle_frame(struct sk_buff **pskb)\n--\nnet/bridge/br_input.c-367-\nnet/bridge/br_input.c:368:\tmemset(skb-\u003ecb, 0, sizeof(struct br_input_skb_cb));\nnet/bridge/br_input.c-369-\tbr_tc_skb_miss_set(skb, false);\n--\nnet/bridge/netfilter/nf_conntrack_bridge.c=133=static void br_skb_cb_save(struct sk_buff *skb, struct br_input_skb_cb *cb,\n--\nnet/bridge/netfilter/nf_conntrack_bridge.c-136-\tmemcpy(cb, skb-\u003ecb, sizeof(*cb));\nnet/bridge/netfilter/nf_conntrack_bridge.c:137:\tmemset(skb-\u003ecb, 0, inet_skb_parm_size);\nnet/bridge/netfilter/nf_conntrack_bridge.c-138-}\n--\nnet/dsa/user.c=914=static netdev_tx_t dsa_user_xmit(struct sk_buff *skb, struct net_device *dev)\n--\nnet/dsa/user.c-920-\nnet/dsa/user.c:921:\tmemset(skb-\u003ecb, 0, sizeof(skb-\u003ecb));\nnet/dsa/user.c-922-\n--\nnet/ipv4/tcp_output.c=1592=static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,\n--\nnet/ipv4/tcp_output.c-1766-\t/* Cleanup our debris for IP stacks */\nnet/ipv4/tcp_output.c:1767:\tmemset(skb-\u003ecb, 0, max(sizeof(struct inet_skb_parm),\nnet/ipv4/tcp_output.c-1768-\t\t\t       sizeof(struct inet6_skb_parm)));\n--\nnet/ipv6/ip6_tunnel.c=808=static int __ip6_tnl_rcv(struct ip6_tnl *tunnel, struct sk_buff *skb,\n--\nnet/ipv6/ip6_tunnel.c-870-\nnet/ipv6/ip6_tunnel.c:871:\tmemset(skb-\u003ecb, 0, sizeof(struct inet6_skb_parm));\nnet/ipv6/ip6_tunnel.c-872-\n--\nnet/l2tp/l2tp_core.c=1223=static int l2tp_xmit_core(struct l2tp_session *session, struct sk_buff *skb, unsigned int *len)\n--\nnet/l2tp/l2tp_core.c-1255-\t/* Reset control buffer */\nnet/l2tp/l2tp_core.c:1256:\tmemset(skb-\u003ecb, 0, sizeof(skb-\u003ecb));\nnet/l2tp/l2tp_core.c-1257-\n--\nnet/mac80211/rx.c=2725=static void ieee80211_deliver_skb_to_local_stack(struct sk_buff *skb,\n--\nnet/mac80211/rx.c-2742-\nnet/mac80211/rx.c:2743:\t\tmemset(skb-\u003ecb, 0, sizeof(skb-\u003ecb));\nnet/mac80211/rx.c-2744-\n--\nnet/mac80211/status.c=943=void ieee80211_tx_monitor(struct ieee80211_local *local, struct sk_buff *skb,\n--\nnet/mac80211/status.c-966-\tskb-\u003eprotocol = htons(ETH_P_802_2);\nnet/mac80211/status.c:967:\tmemset(skb-\u003ecb, 0, sizeof(skb-\u003ecb));\nnet/mac80211/status.c-968-\n--\nnet/netfilter/nf_flow_table_ip.c=838=static int nf_flow_queue_xmit4(struct sk_buff *skb,\n--\nnet/netfilter/nf_flow_table_ip.c-851-\t\trt = dst_rtable(tuplehash-\u003etuple.dst_cache);\nnet/netfilter/nf_flow_table_ip.c:852:\t\tmemset(skb-\u003ecb, 0, sizeof(struct inet_skb_parm));\nnet/netfilter/nf_flow_table_ip.c-853-\t\tIPCB(skb)-\u003eiif = skb-\u003edev-\u003eifindex;\n--\nnet/netfilter/nf_flow_table_ip.c=1167=static int nf_flow_queue_xmit6(struct sk_buff *skb,\n--\nnet/netfilter/nf_flow_table_ip.c-1180-\t\trt = dst_rt6_info(tuplehash-\u003etuple.dst_cache);\nnet/netfilter/nf_flow_table_ip.c:1181:\t\tmemset(skb-\u003ecb, 0, sizeof(struct inet6_skb_parm));\nnet/netfilter/nf_flow_table_ip.c-1182-\t\tIP6CB(skb)-\u003eiif = skb-\u003edev-\u003eifindex;\n--\nnet/nfc/netlink.c=1681=int nfc_vendor_cmd_reply(struct sk_buff *skb)\n--\nnet/nfc/netlink.c-1686-\t/* clear CB data for netlink core to own from now on */\nnet/nfc/netlink.c:1687:\tmemset(skb-\u003ecb, 0, sizeof(skb-\u003ecb));\nnet/nfc/netlink.c-1688-\n--\nnet/wireless/nl80211.c=14339=void __cfg80211_send_event_skb(struct sk_buff *skb, gfp_t gfp)\n--\nnet/wireless/nl80211.c-14347-\t/* clear CB data for netlink core to own from now on */\nnet/wireless/nl80211.c:14348:\tmemset(skb-\u003ecb, 0, sizeof(skb-\u003ecb));\nnet/wireless/nl80211.c-14349-\n--\nnet/wireless/nl80211.c=18784=int cfg80211_vendor_cmd_reply(struct sk_buff *skb)\n--\nnet/wireless/nl80211.c-18790-\t/* clear CB data for netlink core to own from now on */\nnet/wireless/nl80211.c:18791:\tmemset(skb-\u003ecb, 0, sizeof(skb-\u003ecb));\nnet/wireless/nl80211.c-18792-\n--\nnet/wireless/util.c=2906=void cfg80211_send_layer2_update(struct net_device *dev, const u8 *addr)\n--\nnet/wireless/util.c-2934-\tskb-\u003eprotocol = eth_type_trans(skb, dev);\n\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 55868
							output: 34
							thoughts: 22
						

Thoughts:
Okay, let's dive into this. Here's my understanding, presented as a personal thought process, given my expertise:

**Diving into the SKB Allocation Process**

Alright, I'm thinking, where's the best place to start understanding the kernel's memory management for network packets? Well, the standard go-to is always the core, and in this case, the `sk_buff` structure is pretty much the central data structure for network buffers. So, let's take a look at how these things are actually *allocated*. My intuition tells me that `__alloc_skb` is the key function in `net/core/skbuff.c`. I expect this will be the low-level, internal function that handles the nitty-gritty of getting an `sk_buff` instance and its associated data blocks set up. I'm keen to see how the kernel handles the various buffer sizes, any considerations for DMA, and of course, how they deal with alignment and page boundaries. It's time to dig in and see how this foundational part of the networking stack is implemented. I'm looking for memory allocation strategies, how they're managing fragments, and any optimizations they've put in place to ensure maximum efficiency. Let's see how smart they are!



36/3 2026/10/29 04:44 tool grepper
0m Args:
{
  "Expression": "__alloc_skb",
  "PathPrefixes": [
    "net/core/skbuff.c"
  ]
}

Results:
{
  "Output": "net/core/skbuff.c=607=static void *kmalloc_reserve(unsigned int *size, gfp_t flags, int node,\n--\nnet/core/skbuff.c-658-/**\nnet/core/skbuff.c:659: *\t__alloc_skb\t-\tallocate a network buffer\nnet/core/skbuff.c-660- *\t@size: size to allocate\n--\nnet/core/skbuff.c-674- */\nnet/core/skbuff.c:675:struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,\nnet/core/skbuff.c-676-\t\t\t    int flags, int node)\n--\nnet/core/skbuff.c-746-}\nnet/core/skbuff.c:747:EXPORT_SYMBOL(__alloc_skb);\nnet/core/skbuff.c-748-\n--\nnet/core/skbuff.c=762=struct sk_buff *__netdev_alloc_skb(struct net_device *dev, unsigned int len,\n--\nnet/core/skbuff.c-777-\t    (gfp_mask \u0026 (__GFP_DIRECT_RECLAIM | GFP_DMA))) {\nnet/core/skbuff.c:778:\t\tskb = __alloc_skb(len, gfp_mask, SKB_ALLOC_RX, NUMA_NO_NODE);\nnet/core/skbuff.c-779-\t\tif (!skb)\n--\nnet/core/skbuff.c=839=struct sk_buff *napi_alloc_skb(struct napi_struct *napi, unsigned int len)\n--\nnet/core/skbuff.c-855-\t    (gfp_mask \u0026 (__GFP_DIRECT_RECLAIM | GFP_DMA))) {\nnet/core/skbuff.c:856:\t\tskb = __alloc_skb(len, gfp_mask, SKB_ALLOC_RX | SKB_ALLOC_NAPI,\nnet/core/skbuff.c-857-\t\t\t\t  NUMA_NO_NODE);\n--\nnet/core/skbuff.c=2171=struct sk_buff *skb_copy(const struct sk_buff *skb, gfp_t gfp_mask)\n--\nnet/core/skbuff.c-2184-\tsize = skb_end_offset(skb) + skb-\u003edata_len;\nnet/core/skbuff.c:2185:\tn = __alloc_skb(size, gfp_mask,\nnet/core/skbuff.c-2186-\t\t\tskb_alloc_rx_flag(skb), NUMA_NO_NODE);\n--\nnet/core/skbuff.c=2224=struct sk_buff *__pskb_copy_fclone(struct sk_buff *skb, int headroom,\n--\nnet/core/skbuff.c-2228-\tint flags = skb_alloc_rx_flag(skb) | (fclone ? SKB_ALLOC_FCLONE : 0);\nnet/core/skbuff.c:2229:\tstruct sk_buff *n = __alloc_skb(size, gfp_mask, flags, NUMA_NO_NODE);\nnet/core/skbuff.c-2230-\n--\nnet/core/skbuff.c=2508=struct sk_buff *skb_copy_expand(const struct sk_buff *skb,\n--\nnet/core/skbuff.c-2525-\toldheadroom = skb_headroom(skb);\nnet/core/skbuff.c:2526:\tn = __alloc_skb(newheadroom + skb-\u003elen + newtailroom,\nnet/core/skbuff.c-2527-\t\t\tgfp_mask, skb_alloc_rx_flag(skb),\n--\nnet/core/skbuff.c=4840=struct sk_buff *skb_segment(struct sk_buff *head_skb,\n--\nnet/core/skbuff.c-5021-\nnet/core/skbuff.c:5022:\t\t\tnskb = __alloc_skb(hsize + doffset + headroom,\nnet/core/skbuff.c-5023-\t\t\t\t\t   GFP_ATOMIC, skb_alloc_rx_flag(head_skb),\n"
}

37/3 2026/10/29 04:44 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 57296
							output: 36
							thoughts: 36
						

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

**Diving into the SKB Allocation Process**

Okay, so I want to get a granular understanding of exactly what's happening during the `__alloc_skb` call. My instinct is always to go directly to the source, so I'm heading over to the `net/core/skbuff.c` file. Specifically, I'm zeroing in on lines 675 to 730. That block should contain the core initialization logic related to this function. I'm especially keen to see how the Socket Buffer (SKB) is set up – the structure population, memory allocation specifics, and any initial flags or values. I want to build a rock-solid mental model of this critical function. Let's see what the code reveals.



38/3 2026/10/29 04:45 tool read-file
0m Args:
{
  "File": "net/core/skbuff.c",
  "FirstLine": 675,
  "LineCount": 55
}

Results:
{
  "Contents": " 675:\tstruct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,\n 676:\t\t\t\t    int flags, int node)\n 677:\t{\n 678:\t\tstruct sk_buff *skb = NULL;\n 679:\t\tstruct kmem_cache *cache;\n 680:\t\tu8 *data;\n 681:\t\n 682:\t\tif (sk_memalloc_socks() \u0026\u0026 (flags \u0026 SKB_ALLOC_RX))\n 683:\t\t\tgfp_mask |= __GFP_MEMALLOC;\n 684:\t\n 685:\t\tif (flags \u0026 SKB_ALLOC_FCLONE) {\n 686:\t\t\tcache = net_hotdata.skbuff_fclone_cache;\n 687:\t\t\tgoto fallback;\n 688:\t\t}\n 689:\t\tcache = net_hotdata.skbuff_cache;\n 690:\t\tif (unlikely(node != NUMA_NO_NODE \u0026\u0026 node != numa_mem_id()))\n 691:\t\t\tgoto fallback;\n 692:\t\n 693:\t\tif (flags \u0026 SKB_ALLOC_NAPI) {\n 694:\t\t\tskb = napi_skb_cache_get(true);\n 695:\t\t\tif (unlikely(!skb))\n 696:\t\t\t\treturn NULL;\n 697:\t\t} else if (!in_hardirq() \u0026\u0026 !irqs_disabled()) {\n 698:\t\t\tlocal_bh_disable();\n 699:\t\t\tskb = napi_skb_cache_get(false);\n 700:\t\t\tlocal_bh_enable();\n 701:\t\t}\n 702:\t\n 703:\t\tif (!skb) {\n 704:\tfallback:\n 705:\t\t\tskb = kmem_cache_alloc_node(cache, gfp_mask \u0026 ~GFP_DMA, node);\n 706:\t\t\tif (unlikely(!skb))\n 707:\t\t\t\treturn NULL;\n 708:\t\t}\n 709:\t\tskbuff_clear(skb);\n 710:\t\n 711:\t\t/* We do our best to align skb_shared_info on a separate cache\n 712:\t\t * line. It usually works because kmalloc(X \u003e SMP_CACHE_BYTES) gives\n 713:\t\t * aligned memory blocks, unless SLUB/SLAB debug is enabled.\n 714:\t\t * Both skb-\u003ehead and skb_shared_info are cache line aligned.\n 715:\t\t */\n 716:\t\tdata = kmalloc_reserve(\u0026size, gfp_mask, node, skb);\n 717:\t\tif (unlikely(!data))\n 718:\t\t\tgoto nodata;\n 719:\t\t/* kmalloc_size_roundup() might give us more room than requested.\n 720:\t\t * Put skb_shared_info exactly at the end of allocated zone,\n 721:\t\t * to allow max possible filling before reallocation.\n 722:\t\t */\n 723:\t\t__finalize_skb_around(skb, data, size);\n 724:\t\n 725:\t\tif (flags \u0026 SKB_ALLOC_FCLONE) {\n 726:\t\t\tstruct sk_buff_fclones *fclones;\n 727:\t\n 728:\t\t\tfclones = container_of(skb, struct sk_buff_fclones, skb1);\n 729:\t\n"
}

39/3 2026/10/29 04:45 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 58440
							output: 21
							thoughts: 21
						

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

**Memory Management and SKB Initialization**

Right, `skbuff_clear(skb)`... I need to immediately understand what's going on with this `skb`. Let me quickly confirm the purpose of `skbuff_clear`. I know this function is crucial for cleaning up an `sk_buff` struct, but I want to be *absolutely* clear on what parts are zeroed, what resources are released, and if there are any specific considerations depending on the `skb`'s state (e.g., if it's currently part of a fragment chain, has associated metadata, etc.). I need to be sure it's doing the bare minimum for *efficient* recycling of this buffer, without over-allocating, but without running into some nasty corruption later. I also want to double-check that this call respects any attached extensions - is it designed to reset those as well?



40/3 2026/10/29 04:45 tool grepper
0m Args:
{
  "Expression": "static inline void skbuff_clear"
}

Results:
{
  "Output": "net/core/skbuff.c=285=static inline struct sk_buff *napi_skb_cache_get(bool alloc)\n--\nnet/core/skbuff.c-316- */\nnet/core/skbuff.c:317:static inline void skbuff_clear(struct sk_buff *skb)\nnet/core/skbuff.c-318-{\n"
}

41/3 2026/10/29 04:45 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 58795
							output: 36
							thoughts: 25
						

Thoughts:
Okay, let's dive into this. Here's my take on those lines from `net/core/skbuff.c`:

**Analyzing skbuff Structure Initialization**

Alright, I'm thinking, let's zero in on lines 317 to 335 in `net/core/skbuff.c`. I need to refresh my understanding of how `skbuff` structures are likely initialized there. This is a crucial section, probably dealing with allocation or basic setup of a new `skbuff`. Given the surrounding context in the file, my experience suggests this covers the fundamental groundwork for packet buffers, likely defining their initial state and memory regions. I bet we'll see some `kmalloc` calls or equivalent to grab memory, perhaps setting the `head` and `data` pointers within the structure. I expect a careful handling of the `len` and `data_len` fields to reflect the initial state of an empty buffer. I'll need to pay close attention to the use of any flags or meta-data fields that might also get set, which could impact the buffer's subsequent use. It's likely we will see some kind of reference counting setup as well. I'm going to look for any optimizations or pre-initializations as well. I need to be prepared to see how the buffer is integrated within the network stack. I'm going to be looking for a detailed picture of the low-level aspects.



42/3 2026/10/29 04:45 tool read-file
0m Args:
{
  "File": "net/core/skbuff.c",
  "FirstLine": 317,
  "LineCount": 20
}

Results:
{
  "Contents": " 317:\tstatic inline void skbuff_clear(struct sk_buff *skb)\n 318:\t{\n 319:\t\t/* Replace memset(skb, 0, offsetof(struct sk_buff, tail))\n 320:\t\t * with two smaller memset(), with a barrier() between them.\n 321:\t\t * This forces the compiler to inline both calls.\n 322:\t\t */\n 323:\t\tBUILD_BUG_ON(offsetof(struct sk_buff, tail) \u003c= 128);\n 324:\t\tmemset(skb, 0, 128);\n 325:\t\tbarrier();\n 326:\t\tmemset((void *)skb + 128, 0, offsetof(struct sk_buff, tail) - 128);\n 327:\t}\n 328:\t\n 329:\t/**\n 330:\t * napi_skb_cache_get_bulk - obtain a number of zeroed skb heads from the cache\n 331:\t * @skbs: pointer to an at least @n-sized array to fill with skb pointers\n 332:\t * @n: number of entries to provide\n 333:\t *\n 334:\t * Tries to obtain @n \u0026sk_buff entries from the NAPI percpu cache and writes\n 335:\t * the pointers into the provided array @skbs. If there are less entries\n 336:\t * available, tries to replenish the cache and bulk-allocates the diff from\n"
}

43/3 2026/10/29 04:45 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 59549
							output: 34
							thoughts: 25
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Investigating the Location of `cb` Within `struct sk_buff`**

Alright, so I'm looking at some code and I need to understand where `cb` is located within the `sk_buff` structure. My immediate thought is, "Okay, time to consult the source." Let's pull up the `struct sk_buff` definition. This is fundamental; I need to understand the layout, the offsets, and any potential alignment considerations. I'm not looking for a high-level explanation; I need to drill down into the nitty-gritty of the data structure to pin down that `cb` field and see where it lives. Let's see what the header files reveal.



44/3 2026/10/29 04:45 tool grepper
0m Args:
{
  "Expression": "cb\\[48\\]",
  "PathPrefixes": [
    "include/linux/skbuff.h"
  ]
}

Results:
{
  "Output": "include/linux/skbuff.h=892=struct sk_buff {\n--\ninclude/linux/skbuff.h-924-\t */\ninclude/linux/skbuff.h:925:\tchar\t\t\tcb[48] __aligned(8);\ninclude/linux/skbuff.h-926-\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 59865
							output: 22
							thoughts: 464
						

Thoughts:
**Analyzing the Packet Reception in Virtio-VSOCK**

Okay, so I'm thinking about how the `cb` structure within an `sk_buff` is handled. It's clear that it resides somewhere between offset 0 and `tail`. Therefore, `skbuff_clear` zeroes up to `tail`, which effectively covers the `cb` structure. Now, I need to understand how this interacts with the data flow in `virtio_transport_recv_pkt`.

Specifically, I'm trying to trace the path of a datagram packet. The code snippet for `VIRTIO_VSOCK_TYPE_DGRAM` in `virtio_transport_recv_pkt` is what I'm interested in: I can see that this path checks the packet type and operation, finds a bound socket and performs several checks, including whether the socket is dead, and source is allowed. Then it sets `VIRTIO_VSOCK_SKB_CB(skb)->to_host` and queues the `skb` using `sock_queue_rcv_skb`, and it frees the skb if that function returns an error.

The tricky part is that I'm drawing a blank on where the `skb` is coming from in the first place, and I should be able to figure that out with a little bit of investigation. So, I need to figure out the origin of that `skb` parameter. I need to trace the call chain. I'll search for all calls to `virtio_transport_recv_pkt` to see where it's being invoked from. I think that will give me some insight.



46/3 2026/10/29 04:45 tool grepper
0m Args:
{
  "Expression": "virtio_transport_recv_pkt"
}

Results:
{
  "Output": "drivers/vhost/vsock.c=550=static void vhost_vsock_handle_tx_kick(struct vhost_work *work)\n--\ndrivers/vhost/vsock.c-610-\t\t    vhost_transport_get_local_cid())\ndrivers/vhost/vsock.c:611:\t\t\tvirtio_transport_recv_pkt(\u0026vhost_transport, skb,\ndrivers/vhost/vsock.c-612-\t\t\t\t\t\t  vsock-\u003enet);\n--\ninclude/linux/virtio_vsock.h=287=void virtio_transport_destruct(struct vsock_sock *vsk);\ninclude/linux/virtio_vsock.h-288-\ninclude/linux/virtio_vsock.h:289:void virtio_transport_recv_pkt(struct virtio_transport *t,\ninclude/linux/virtio_vsock.h-290-\t\t\t       struct sk_buff *skb, struct net *net);\n--\ninclude/trace/events/vsock_virtio_transport_common.h=39=TRACE_EVENT(virtio_transport_alloc_pkt,\n--\ninclude/trace/events/vsock_virtio_transport_common.h-88-\ninclude/trace/events/vsock_virtio_transport_common.h:89:TRACE_EVENT(virtio_transport_recv_pkt,\ninclude/trace/events/vsock_virtio_transport_common.h-90-\tTP_PROTO(\n--\nnet/vmw_vsock/virtio_transport.c=675=static void virtio_transport_rx_work(struct work_struct *work)\n--\nnet/vmw_vsock/virtio_transport.c-729-\t\t\t */\nnet/vmw_vsock/virtio_transport.c:730:\t\t\tvirtio_transport_recv_pkt(\u0026virtio_transport, skb, NULL);\nnet/vmw_vsock/virtio_transport.c-731-\t\t}\n--\nnet/vmw_vsock/virtio_transport_common.c=1881=virtio_transport_dgram_source_allow(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-1909- */\nnet/vmw_vsock/virtio_transport_common.c:1910:void virtio_transport_recv_pkt(struct virtio_transport *t,\nnet/vmw_vsock/virtio_transport_common.c-1911-\t\t\t       struct sk_buff *skb, struct net *net)\n--\nnet/vmw_vsock/virtio_transport_common.c-1923-\nnet/vmw_vsock/virtio_transport_common.c:1924:\ttrace_virtio_transport_recv_pkt(src.svm_cid, src.svm_port,\nnet/vmw_vsock/virtio_transport_common.c-1925-\t\t\t\t\tdst.svm_cid, dst.svm_port,\n--\nnet/vmw_vsock/virtio_transport_common.c-2053-}\nnet/vmw_vsock/virtio_transport_common.c:2054:EXPORT_SYMBOL_GPL(virtio_transport_recv_pkt);\nnet/vmw_vsock/virtio_transport_common.c-2055-\n--\nnet/vmw_vsock/vsock_loopback.c=124=static void vsock_loopback_work(struct work_struct *work)\n--\nnet/vmw_vsock/vsock_loopback.c-142-\t\tvirtio_transport_deliver_tap_pkt(skb);\nnet/vmw_vsock/vsock_loopback.c:143:\t\tvirtio_transport_recv_pkt(\u0026loopback_transport, skb,\nnet/vmw_vsock/vsock_loopback.c-144-\t\t\t\t\t  sock_net(skb-\u003esk));\n"
}

47/3 2026/10/29 04:45 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 61698
							output: 38
							thoughts: 1029
						

Thoughts:
**Analyzing Virtio vsock SKB Handling**

Okay, so I'm diving into how `virtio_transport_recv_pkt` is used, specifically focusing on the receive path in `net/vmw_vsock/virtio_transport.c` within the `virtio_transport_rx_work` context.  I'm particularly interested in how the SKB is handled.

My first observation is that there are three distinct places that call the function of interest, but my focus is on the second of the three identified.

Now, looking at `virtio_vsock_rx_fill`, I see the SKB allocation happening with `virtio_vsock_alloc_skb`. A series of modifications are applied to the `skb`, and it immediately raised a red flag. The key sequence is `virtio_vsock_skb_put` puts `total_len - VIRTIO_VSOCK_SKB_HEADROOM` bytes into the SKB, followed by a `skb_push(skb, VIRTIO_VSOCK_SKB_HEADROOM)` and then `skb_pull(skb, VIRTIO_VSOCK_SKB_HEADROOM)`.

This looks extremely suspicious because, from the initial allocation, let's step back and consider how the SKB is created. Looking at `__virtio_vsock_alloc_skb_with_frags`, the interesting things are: The `alloc_skb_with_frags` function allocates `data_len` frags if `data_len > 0`. I know that if `size` is small, it allocates a linear SKB. Let's make sure the size isn't "small". `VIRTIO_VSOCK_MAX_PKT_BUF_SIZE` is 64KB!  So, the size is always much greater than 32KB.  Therefore, it calls `__virtio_vsock_alloc_skb_with_frags`.

In `__virtio_vsock_alloc_skb_with_frags`, I see that `skb_reserve(skb, VIRTIO_VSOCK_SKB_HEADROOM)` is called and crucially, `skb->data_len` is set to `data_len` which is 64KB, which is significantly non-zero! The `skb->len` is still zero!

Okay, so the initial `skb->len` is set to zero, but the `skb->data_len` is set to `data_len` or 64KB. This means that we have allocated pages for the SKB data portion, and now we need to see what `alloc_skb_with_frags` *actually* does. I need to go look at the implementation of `alloc_skb_with_frags` in `net/core/skbuff.c` to understand exactly how the `skb->data_len` field is set and what the allocated memory actually looks like. The initial sequence of operations seems like a convoluted way of managing the SKB's length/data pointers. I need to trace through the SKB allocation and manipulation more carefully to be certain I understand what's happening.



48/3 2026/10/29 04:45 tool grepper
0m Args:
{
  "Expression": "alloc_skb_with_frags",
  "PathPrefixes": [
    "net/core/skbuff.c"
  ]
}

Results:
{
  "Output": "net/core/skbuff.c=6864=EXPORT_SYMBOL_GPL(skb_mpls_dec_ttl);\n--\nnet/core/skbuff.c-6866-/**\nnet/core/skbuff.c:6867: * alloc_skb_with_frags - allocate skb with page frags\nnet/core/skbuff.c-6868- *\n--\nnet/core/skbuff.c-6876- */\nnet/core/skbuff.c:6877:struct sk_buff *alloc_skb_with_frags(unsigned long header_len,\nnet/core/skbuff.c-6878-\t\t\t\t     unsigned long data_len,\n--\nnet/core/skbuff.c-6929-}\nnet/core/skbuff.c:6930:EXPORT_SYMBOL(alloc_skb_with_frags);\nnet/core/skbuff.c-6931-\n"
}

49/3 2026/10/29 04:45 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 63718
							output: 37
							thoughts: 27
						

Thoughts:
Okay, let's dive into this. I'm going to focus on lines 6877 to 6930 of `net/core/skbuff.c`.

**Diving Deep into skbuff.c: Defragmentation Details**

Alright, my brain's engaged. I need to understand this fragment reassembly logic. Scanning those lines, it looks like this section is dealing with handling fragmented packets within the kernel. Specifically, I'm betting it's the core reassembly process. I have a hunch this code deals with situations where a packet arrives in pieces – like a large IP packet broken up to traverse a network path with a smaller MTU. It needs to recombine those fragments before handing the whole packet to the higher layers. I'm going to look for key data structures – I'd expect to see references to the `skb` (socket buffer) and possibly some kind of fragment list or descriptor. I'm mentally parsing this now and anticipating this code will be responsible for things like:

*   Checking fragment headers for flags (MF - More Fragments, DF - Don't Fragment) and offsets.
*   Allocating the reassembled `skb` header.
*   Copying the payload fragments to a buffer in memory.
*   Checking for timeouts or handling partial fragments if some are lost.
*   Handling potential overlap if fragments arrive out of order, or are incorrectly reassembled.
*   Checking the fragments for packet size limits.

I'll be looking for any hints about potential performance bottlenecks in this part of the code, or any specific security implications related to the reassembly process. I'm curious if this code makes any optimizations or uses any heuristics to try and handle fragmentation in the most efficient manner, such as memory pooling, or other memory usage optimizations. Time to read!



50/3 2026/10/29 04:45 tool read-file
0m Args:
{
  "File": "net/core/skbuff.c",
  "FirstLine": 6877,
  "LineCount": 55
}

Results:
{
  "Contents": "6877:\tstruct sk_buff *alloc_skb_with_frags(unsigned long header_len,\n6878:\t\t\t\t\t     unsigned long data_len,\n6879:\t\t\t\t\t     int order,\n6880:\t\t\t\t\t     int *errcode,\n6881:\t\t\t\t\t     gfp_t gfp_mask)\n6882:\t{\n6883:\t\tunsigned long chunk;\n6884:\t\tstruct sk_buff *skb;\n6885:\t\tstruct page *page;\n6886:\t\tint nr_frags = 0;\n6887:\t\n6888:\t\t*errcode = -EMSGSIZE;\n6889:\t\tif (unlikely(data_len \u003e MAX_SKB_FRAGS * (PAGE_SIZE \u003c\u003c order)))\n6890:\t\t\treturn NULL;\n6891:\t\n6892:\t\t*errcode = -ENOBUFS;\n6893:\t\tskb = alloc_skb(header_len, gfp_mask);\n6894:\t\tif (!skb)\n6895:\t\t\treturn NULL;\n6896:\t\n6897:\t\twhile (data_len) {\n6898:\t\t\tif (nr_frags == MAX_SKB_FRAGS)\n6899:\t\t\t\tgoto failure;\n6900:\t\t\twhile (order \u0026\u0026 PAGE_ALIGN(data_len) \u003c (PAGE_SIZE \u003c\u003c order))\n6901:\t\t\t\torder--;\n6902:\t\n6903:\t\t\tif (order) {\n6904:\t\t\t\tpage = alloc_pages((gfp_mask \u0026 ~__GFP_DIRECT_RECLAIM) |\n6905:\t\t\t\t\t\t   __GFP_COMP |\n6906:\t\t\t\t\t\t   __GFP_NOWARN,\n6907:\t\t\t\t\t\t   order);\n6908:\t\t\t\tif (!page) {\n6909:\t\t\t\t\torder--;\n6910:\t\t\t\t\tcontinue;\n6911:\t\t\t\t}\n6912:\t\t\t} else {\n6913:\t\t\t\tpage = alloc_page(gfp_mask);\n6914:\t\t\t\tif (!page)\n6915:\t\t\t\t\tgoto failure;\n6916:\t\t\t}\n6917:\t\t\tchunk = min_t(unsigned long, data_len,\n6918:\t\t\t\t      PAGE_SIZE \u003c\u003c order);\n6919:\t\t\tskb_fill_page_desc(skb, nr_frags, page, 0, chunk);\n6920:\t\t\tnr_frags++;\n6921:\t\t\tskb-\u003etruesize += (PAGE_SIZE \u003c\u003c order);\n6922:\t\t\tdata_len -= chunk;\n6923:\t\t}\n6924:\t\treturn skb;\n6925:\t\n6926:\tfailure:\n6927:\t\tkfree_skb(skb);\n6928:\t\treturn NULL;\n6929:\t}\n6930:\tEXPORT_SYMBOL(alloc_skb_with_frags);\n6931:\t\n"
}

51/3 2026/10/29 04:45 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 64936
							output: 42
							thoughts: 1133
						

Thoughts:
**Analyzing Virtio vsock Buffer Handling and KMSAN Concerns**

Okay, so I'm looking closely at how virtio-vsock handles incoming data, specifically how it interacts with the buffer and KMSAN, because there are a few things that need to be ironed out to avoid security problems.

First, I see that `alloc_pages` is used but, crucially, these pages aren't initialized to zero. That's immediately interesting, especially considering DMA operations. Then, the process: a virtio device DMA-writes data into a buffer. Right, that's normal, but the question becomes: how much data did the device actually *write*? Let's trace it through `virtio_transport_rx_work`.

The code retrieves a buffer with `virtqueue_get_buf(vq, &len)`.  The `len` variable is super important here because it's the *actual* number of bytes written by the device. Okay, got it. Then, there's a quick check for short or long packets, and it immediately flags a possible problem for dropped packets.

Now, it pulls the header and then the payload length, which becomes `payload_len`. The first check is, if `payload_len > len - sizeof(*hdr)` it drops. The second check is, what if `payload_len <= len - sizeof(*hdr)`. In that case, `pskb_trim_unique(skb, payload_len)` is called, which trims the SKB to the payload length. This is where I have to think about the situation more... what was the original `skb->len` before the trim?

I looked into `virtio_vsock_rx_fill`. The buffer is setup via the virtqueue. Crucially, the code calls `skb_pull(skb, VIRTIO_VSOCK_SKB_HEADROOM);`. This leaves `skb->len` at `total_len - VIRTIO_VSOCK_SKB_HEADROOM` which is equivalent to `VIRTIO_VSOCK_MAX_PKT_BUF_SIZE` (which is 64KB).

Okay, so, let's break this down.  If the device says it wrote 100 bytes (header + payload), but the header says the payload is only 50 bytes, then `pskb_trim_unique(skb, 50)` trims it.  But what if the device *only* wrote 50 bytes and the header said 200? The conditional `payload_len > len - sizeof(*hdr)` becomes `200 > 50 - 44` which causes a drop, as expected.

What if the device wrote 64 bytes (header + 20 bytes written) and the header says the payload is 20 bytes? `20 <= 64 - 44` - that checks out. The device wrote 64 bytes into the buffer and only 20 bytes is the payload.

Now, my mind goes to the bigger question. What if the `skb` is non-linear and we have page frags, and how does this affect KMSAN. This is where the red flag comes up: KMSAN *doesn't* instrument virtio DMA or host memory writes. When a virtio device DMA's into guest memory, does KMSAN even know it was initialized? It is very likely that the buffer pages are uninitialized before virtio writes into them. That's a major vulnerability risk if the hypervisor or guest is not taking measures to handle this scenario. So, how does virtio actually handle these incoming buffers with KMSAN? Does it use `kmsan_unpoison_memory` or something similar?  Let's look for how `kmsan_unpoison` is called within virtio code. We have to check if virtio handles potential initialization issues before exposing this data.



52/3 2026/10/29 04:46 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "kmsan_unpoison",
  "PathPrefixes": [
    "drivers/virtio/",
    "net/vmw_vsock/"
  ]
}

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

53/3 2026/10/29 04:46 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 66962
							output: 38
							thoughts: 26
						

Thoughts:
Okay, here's my summary, presented as a stream of thought:

**KMSAN Investigation - Where to Begin?**

Alright, time to dive in. My focus is `kmsan_unpoison_memory`, and I need to figure out its impact. First things first, I need to understand its scope. Let's start with a focused search. Given that it's related to memory handling, it's most likely used within the kernel. My best starting point is likely the `net/` and `drivers/` directories. These areas are notorious for memory-related bugs and interactions, so it makes sense to check there first. Let's limit the search to those sub-trees to get a manageable set of results and not waste time sifting through irrelevant code. This search should provide a good initial overview. From there, I can analyze the usages and determine what's going on.



54/3 2026/10/29 04:46 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "kmsan_unpoison_memory",
  "PathPrefixes": [
    "net/",
    "drivers/vhost/"
  ]
}

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

55/3 2026/10/29 04:46 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 67229
							output: 44
							thoughts: 2181
						

Thoughts:
**KMSAN Assessment: A Deep Dive**

Okay, so I need to decide if this patch requires KMSAN. The instructions are clear: if KASAN or other standard bug detectors would catch it, then no KMSAN is needed. KMSAN is for *uninitialized* memory issues that slip through the cracks of those other tools. Got it. I need to be thorough.

First, I need to understand that DMA unpoisoning isn't a factor here; the code I'm looking at doesn't directly deal with DMA, or I can assume that DMA layer/virtqueue/vring/kmsan intercepts it.

Now, let's break down the patch point by point, following the KMSAN guidelines.

1.  **Kernel structures to userspace:** My initial glance at `vsock_do_ioctl` shows a scalar `n_bytes` being sent to userspace, initialized on all paths. That's a simple `put_user` and seems clean. Moving on, I then examined `virtio_transport_dgram_dequeue`.  At first I was worried that `msg->msg_name` might be an issue. But the more I looked, I realized that the whole `struct sockaddr_vm` is zeroed out by `memset` before any values are written. Phew! No uninitialized memory is being leaked to user space there.

    Then I needed to consider the data within the `skb` used by `skb_copy_datagram_iter`. Where did *that* data come from? A quick check of `virtio_transport_dgram_enqueue` and its subsequent function calls reveals the data origin. I need to check how the data gets populated inside the kernel to see if there is uninitialized data. Specifically, I'm checking the lines 340-420 of `net/vmw_vsock/virtio_transport_common.c`.



56/3 2026/10/29 04:46 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/virtio_transport_common.c",
  "FirstLine": 360,
  "LineCount": 70
}

Results:
{
  "Contents": " 360:\t\t\t/* Do not send zero length OP_RW pkt */\n 361:\t\t\tif (pkt_len == 0 \u0026\u0026 info-\u003eop == VIRTIO_VSOCK_OP_RW)\n 362:\t\t\t\treturn pkt_len;\n 363:\t\t}\n 364:\t\n 365:\t\tif (info-\u003emsg \u0026\u0026 (info-\u003emsg-\u003emsg_flags \u0026 MSG_ZEROCOPY)) {\n 366:\t\t\t/* If 'info-\u003emsg' is not NULL, this is only VIRTIO_VSOCK_OP_RW.\n 367:\t\t\t * 'MSG_ZEROCOPY' flag handling here is based on the same flag\n 368:\t\t\t * handling from 'tcp_sendmsg_locked()'.\n 369:\t\t\t */\n 370:\t\t\tif (info-\u003emsg-\u003emsg_ubuf) {\n 371:\t\t\t\tuarg = info-\u003emsg-\u003emsg_ubuf;\n 372:\t\t\t\tcan_zcopy = virtio_transport_can_zcopy(t_ops, info, pkt_len);\n 373:\t\t\t} else if (sock_flag(sk_vsock(vsk), SOCK_ZEROCOPY)) {\n 374:\t\t\t\tuarg = msg_zerocopy_realloc(sk_vsock(vsk), pkt_len,\n 375:\t\t\t\t\t\t\t    NULL, false);\n 376:\t\t\t\tif (!uarg) {\n 377:\t\t\t\t\tif (info-\u003etype != VIRTIO_VSOCK_TYPE_DGRAM)\n 378:\t\t\t\t\t\tvirtio_transport_put_credit(vvs, pkt_len);\n 379:\t\t\t\t\treturn -ENOMEM;\n 380:\t\t\t\t}\n 381:\t\n 382:\t\t\t\tcan_zcopy = virtio_transport_can_zcopy(t_ops, info, pkt_len);\n 383:\t\t\t\tif (!can_zcopy)\n 384:\t\t\t\t\tuarg_to_msgzc(uarg)-\u003ezerocopy = 0;\n 385:\t\n 386:\t\t\t\thave_uref = true;\n 387:\t\t\t}\n 388:\t\n 389:\t\t\t/* 'can_zcopy' means that this transmission will be\n 390:\t\t\t * in zerocopy way (e.g. using 'frags' array).\n 391:\t\t\t */\n 392:\t\t\tif (can_zcopy)\n 393:\t\t\t\tmax_skb_len = min_t(u32, VIRTIO_VSOCK_MAX_PKT_BUF_SIZE,\n 394:\t\t\t\t\t\t    (MAX_SKB_FRAGS * PAGE_SIZE));\n 395:\t\t}\n 396:\t\n 397:\t\trest_len = pkt_len;\n 398:\t\n 399:\t\tdo {\n 400:\t\t\tstruct sk_buff *skb;\n 401:\t\t\tsize_t skb_len;\n 402:\t\n 403:\t\t\t/* Save iterator state in case allocation or transmission fails\n 404:\t\t\t * so we can restore it and retry.\n 405:\t\t\t */\n 406:\t\t\tif (info-\u003emsg)\n 407:\t\t\t\tiov_iter_save_state(\u0026info-\u003emsg-\u003emsg_iter, \u0026msg_iter_state);\n 408:\t\n 409:\t\t\tskb_len = min(max_skb_len, rest_len);\n 410:\t\n 411:\t\t\t/* Note: virtio_transport_alloc_skb() can advance info-\u003emsg-\u003emsg_iter\n 412:\t\t\t * even if it fails (e.g. partial GUP success).\n 413:\t\t\t */\n 414:\t\t\tskb = virtio_transport_alloc_skb(info, skb_len, can_zcopy,\n 415:\t\t\t\t\t\t\t uarg,\n 416:\t\t\t\t\t\t\t src_cid, src_port,\n 417:\t\t\t\t\t\t\t dst_cid, dst_port);\n 418:\t\t\tif (!skb) {\n 419:\t\t\t\tret = -ENOMEM;\n 420:\t\t\t\tbreak;\n 421:\t\t\t}\n 422:\t\n 423:\t\t\tif (info-\u003etype == VIRTIO_VSOCK_TYPE_DGRAM) {\n 424:\t\t\t\tspin_lock_bh(\u0026vvs-\u003etx_lock);\n 425:\t\t\t\tvvs-\u003ebytes_unsent += skb_len;\n 426:\t\t\t\tspin_unlock_bh(\u0026vvs-\u003etx_lock);\n 427:\t\t\t\tVIRTIO_VSOCK_SKB_CB(skb)-\u003etx_accounted = true;\n 428:\t\t\t\tskb-\u003edestructor = virtio_transport_dgram_skb_destructor;\n 429:\t\t\t} else {\n"
}

57/3 2026/10/29 04:46 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 71067
							output: 44
							thoughts: 56
						

Thoughts:
Here's my summary, as if I were thinking these thoughts:

**Analyzing the Virtio Transport SKB Allocation**

Okay, let's dive into how `virtio_transport_alloc_skb` handles the user-space payload copying process. My primary concern, as always with this kind of low-level networking code, is efficiency and the avoidance of unnecessary memory copies. Let's start with a focused examination of lines 260 to 295 within `net/vmw_vsock/virtio_transport_common.c`. This section should reveal the core logic of the SKB allocation and population. I'll be looking specifically for any data copy operations from user space, and then I can check what APIs are used to achieve it and compare their performance profile. I'll need to confirm if we're dealing with direct DMA or any intermediate buffers, and assess the memory barriers involved. It's time to dig in.



58/3 2026/10/29 04:46 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/virtio_transport_common.c",
  "FirstLine": 260,
  "LineCount": 40
}

Results:
{
  "Contents": " 260:\t\n 261:\t\tif (info-\u003emsg \u0026\u0026 payload_len \u003e 0) {\n 262:\t\t\tint err;\n 263:\t\n 264:\t\t\t/* Bind the zerocopy lifetime before filling frags so error\n 265:\t\t\t * rollback frees managed fixed-buffer pages through\n 266:\t\t\t * the uarg-aware path.\n 267:\t\t\t */\n 268:\t\t\tskb_zcopy_set(skb, uarg, NULL);\n 269:\t\n 270:\t\t\terr = virtio_transport_fill_skb(skb, info, payload_len, zcopy);\n 271:\t\t\tif (err)\n 272:\t\t\t\tgoto out;\n 273:\t\n 274:\t\t\tif (msg_data_left(info-\u003emsg) == 0 \u0026\u0026\n 275:\t\t\t    info-\u003etype == VIRTIO_VSOCK_TYPE_SEQPACKET) {\n 276:\t\t\t\tstruct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);\n 277:\t\n 278:\t\t\t\thdr-\u003eflags |= cpu_to_le32(VIRTIO_VSOCK_SEQ_EOM);\n 279:\t\n 280:\t\t\t\tif (info-\u003emsg-\u003emsg_flags \u0026 MSG_EOR)\n 281:\t\t\t\t\thdr-\u003eflags |= cpu_to_le32(VIRTIO_VSOCK_SEQ_EOR);\n 282:\t\t\t}\n 283:\t\t}\n 284:\t\n 285:\t\tif (info-\u003ereply)\n 286:\t\t\tvirtio_vsock_skb_set_reply(skb);\n 287:\t\n 288:\t\ttrace_virtio_transport_alloc_pkt(src_cid, src_port,\n 289:\t\t\t\t\t\t dst_cid, dst_port,\n 290:\t\t\t\t\t\t payload_len,\n 291:\t\t\t\t\t\t info-\u003etype,\n 292:\t\t\t\t\t\t info-\u003eop,\n 293:\t\t\t\t\t\t info-\u003eflags,\n 294:\t\t\t\t\t\t zcopy);\n 295:\t\n 296:\t\treturn skb;\n 297:\tout:\n 298:\t\tkfree_skb(skb);\n 299:\t\treturn NULL;\n"
}

59/3 2026/10/29 04:46 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 71965
							output: 23
							thoughts: 31
						

Thoughts:
Okay, let's dive into this. I'm thinking, "Right, I need to understand this `virtio_transport_fill_skb` function." My immediate focus is on its purpose. I know the virtio transport layer is critical, so figuring out exactly what this function *does* within that context is key. Let me pull up the definition...  I'll start with the code and see what stands out.



60/3 2026/10/29 04:47 tool grepper
0m Args:
{
  "Expression": "virtio_transport_fill_skb"
}

Results:
{
  "Output": "net/vmw_vsock/virtio_transport_common.c=51=static bool virtio_transport_can_zcopy(const struct virtio_transport *t_ops,\n--\nnet/vmw_vsock/virtio_transport_common.c-79-\nnet/vmw_vsock/virtio_transport_common.c:80:static int virtio_transport_fill_skb(struct sk_buff *skb,\nnet/vmw_vsock/virtio_transport_common.c-81-\t\t\t\t     struct virtio_vsock_pkt_info *info,\n--\nnet/vmw_vsock/virtio_transport_common.c=220=static struct sk_buff *virtio_transport_alloc_skb(struct virtio_vsock_pkt_info *info,\n--\nnet/vmw_vsock/virtio_transport_common.c-269-\nnet/vmw_vsock/virtio_transport_common.c:270:\t\terr = virtio_transport_fill_skb(skb, info, payload_len, zcopy);\nnet/vmw_vsock/virtio_transport_common.c-271-\t\tif (err)\n"
}

61/3 2026/10/29 04:47 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 72417
							output: 43
							thoughts: 31
						

Thoughts:
Okay, let's dive into this. I'm going to take a look at the `net/vmw_vsock/virtio_transport_common.c` file, specifically lines 80 through 115. I need to understand what's happening in that section. Given my expertise, I'm expecting this to be some core transport functionality related to the virtio interface for the vsock protocol. I anticipate this will be dealing with common aspects shared between different virtio transport implementations within the vsock subsystem. Perhaps initialization routines, shared error handling, or resource management related to the virtio queue or device. I'll be looking for any indications of data buffering, interrupt handling, or interactions with the virtio driver layer. I'm keen to see if there are any specific optimizations or considerations made for the virtual environment. Ultimately, my goal is to grasp the foundational logic upon which the higher-level vsock communication rests, and how it's specifically managed using the virtio interface. Let's see what these lines reveal.



62/3 2026/10/29 04:47 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/virtio_transport_common.c",
  "FirstLine": 80,
  "LineCount": 35
}

Results:
{
  "Contents": "  80:\tstatic int virtio_transport_fill_skb(struct sk_buff *skb,\n  81:\t\t\t\t\t     struct virtio_vsock_pkt_info *info,\n  82:\t\t\t\t\t     size_t len,\n  83:\t\t\t\t\t     bool zcopy)\n  84:\t{\n  85:\t\tstruct msghdr *msg = info-\u003emsg;\n  86:\t\n  87:\t\tif (zcopy)\n  88:\t\t\treturn __zerocopy_sg_from_iter(msg, NULL, skb,\n  89:\t\t\t\t\t\t       \u0026msg-\u003emsg_iter, len, NULL);\n  90:\t\n  91:\t\tvirtio_vsock_skb_put(skb, len);\n  92:\t\treturn skb_copy_datagram_from_iter_full(skb, 0, \u0026msg-\u003emsg_iter, len);\n  93:\t}\n  94:\t\n  95:\tstatic void virtio_transport_init_hdr(struct sk_buff *skb,\n  96:\t\t\t\t\t      struct virtio_vsock_pkt_info *info,\n  97:\t\t\t\t\t      size_t payload_len,\n  98:\t\t\t\t\t      u32 src_cid,\n  99:\t\t\t\t\t      u32 src_port,\n 100:\t\t\t\t\t      u32 dst_cid,\n 101:\t\t\t\t\t      u32 dst_port)\n 102:\t{\n 103:\t\tstruct virtio_vsock_hdr *hdr;\n 104:\t\n 105:\t\thdr = virtio_vsock_hdr(skb);\n 106:\t\thdr-\u003etype\t= cpu_to_le16(info-\u003etype);\n 107:\t\thdr-\u003eop\t\t= cpu_to_le16(info-\u003eop);\n 108:\t\thdr-\u003esrc_cid\t= cpu_to_le64(src_cid);\n 109:\t\thdr-\u003edst_cid\t= cpu_to_le64(dst_cid);\n 110:\t\thdr-\u003esrc_port\t= cpu_to_le32(src_port);\n 111:\t\thdr-\u003edst_port\t= cpu_to_le32(dst_port);\n 112:\t\thdr-\u003eflags\t= cpu_to_le32(info-\u003eflags);\n 113:\t\thdr-\u003elen\t= cpu_to_le32(payload_len);\n 114:\t\thdr-\u003ebuf_alloc\t= cpu_to_le32(0);\n"
}

63/3 2026/10/29 04:47 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 73347
							output: 44
							thoughts: 49
						

Thoughts:
Here's my take on those thoughts, keeping in mind my expertise in this area:

**Initial Assessment of `virtio_transport_init_hdr`**

Okay, so I'm looking at `virtio_transport_init_hdr`, and the obvious function of this routine is to initialize the header fields. It's the standard first step, laying the groundwork for data transmission. I need to understand what those fields are and how they're set. Let's dig into the code itself to see what's happening. My experience suggests that this is the first step when setting up the transport layer, and as an expert, I know that getting this step right is fundamental. I need to make sure the base structure is set up properly before the more advanced and nuanced steps come into play. Let's start with lines 110-120 of `net/vmw_vsock/virtio_transport_common.c` to understand the initialization process in more detail.



64/3 2026/10/29 04:47 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/virtio_transport_common.c",
  "FirstLine": 110,
  "LineCount": 15
}

Results:
{
  "Contents": " 110:\t\thdr-\u003esrc_port\t= cpu_to_le32(src_port);\n 111:\t\thdr-\u003edst_port\t= cpu_to_le32(dst_port);\n 112:\t\thdr-\u003eflags\t= cpu_to_le32(info-\u003eflags);\n 113:\t\thdr-\u003elen\t= cpu_to_le32(payload_len);\n 114:\t\thdr-\u003ebuf_alloc\t= cpu_to_le32(0);\n 115:\t\thdr-\u003efwd_cnt\t= cpu_to_le32(0);\n 116:\t}\n 117:\t\n 118:\t/* Packet capture */\n 119:\tstatic struct sk_buff *virtio_transport_build_skb(void *opaque)\n 120:\t{\n 121:\t\tstruct virtio_vsock_hdr *pkt_hdr;\n 122:\t\tstruct sk_buff *pkt = opaque;\n 123:\t\tstruct af_vsockmon_hdr *hdr;\n 124:\t\tstruct sk_buff *skb;\n"
}

65/3 2026/10/29 04:47 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 73941
							output: 37
							thoughts: 557
						

Thoughts:
**My Analysis of Virtio vsock Header Initialization and Vhost vsock Changes**

Okay, so I'm looking at this code and breaking down what's happening. First, `virtio_transport_init_hdr` is responsible for setting up every single field within the `struct virtio_vsock_hdr`. That includes `type`, `op`, `src_cid`, `dst_cid`, `src_port`, `dst_port`, `flags`, `len`, `buf_alloc`, and `fwd_cnt`. It's crucial, as the header, `virtio_vsock_hdr(skb)`, sits right at `skb->head`. A correct header is fundamental to the protocol's function.

Now, let's look at what's been changed in `vhost/vsock.c`. It looks like some new features are being introduced. Specifically, `VIRTIO_VSOCK_F_DGRAM` has been added to the feature set, alongside `VIRTIO_VSOCK_F_SEQPACKET`. It seems the `struct vhost_vsock` now includes `dgram_allow` as a new field. This indicates support for datagram-based communication, which wasn't present before.

Finally, I'm checking out what `vhost_transport_do_send_pkt` is doing. Specifically between lines 170 and 220. It appears there's a new check within `vhost_transport_do_send_pkt`. If the `payload_len` exceeds the available buffer, and the packet type is a datagram, the code now immediately frees the SKB and adds the descriptor to the used queue. So, datagrams *must* fit within a single descriptor chain, a hard constraint. If they don't, they're simply discarded. This is the crucial difference to keep in mind for datagrams. I need to be careful when processing datagram traffic.



66/3 2026/10/29 04:48 tool read-file
0m Args:
{
  "File": "drivers/vhost/vsock.c",
  "FirstLine": 170,
  "LineCount": 55
}

Results:
{
  "Contents": " 170:\t\t\t}\n 171:\t\n 172:\t\t\tiov_len = iov_length(\u0026vq-\u003eiov[out], in);\n 173:\t\t\tif (iov_len \u003c sizeof(*hdr)) {\n 174:\t\t\t\tkfree_skb(skb);\n 175:\t\t\t\tvq_err(vq, \"Buffer len [%zu] too small\\n\", iov_len);\n 176:\t\t\t\tbreak;\n 177:\t\t\t}\n 178:\t\n 179:\t\t\tiov_iter_init(\u0026iov_iter, ITER_DEST, \u0026vq-\u003eiov[out], in, iov_len);\n 180:\t\t\toffset = VIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset;\n 181:\t\t\tpayload_len = skb-\u003elen - offset;\n 182:\t\t\thdr = virtio_vsock_hdr(skb);\n 183:\t\n 184:\t\t\t/* If the packet is greater than the space available in the\n 185:\t\t\t * buffer, we split it using multiple buffers.\n 186:\t\t\t */\n 187:\t\t\tif (payload_len \u003e iov_len - sizeof(*hdr)) {\n 188:\t\t\t\t/* Datagrams must fit in a single descriptor chain. */\n 189:\t\t\t\tif (le16_to_cpu(hdr-\u003etype) == VIRTIO_VSOCK_TYPE_DGRAM) {\n 190:\t\t\t\t\tkfree_skb(skb);\n 191:\t\t\t\t\tvhost_add_used(vq, head, 0);\n 192:\t\t\t\t\tadded = true;\n 193:\t\t\t\t\tcontinue;\n 194:\t\t\t\t}\n 195:\t\n 196:\t\t\t\tpayload_len = iov_len - sizeof(*hdr);\n 197:\t\n 198:\t\t\t\t/* As we are copying pieces of large packet's buffer to\n 199:\t\t\t\t * small rx buffers, headers of packets in rx queue are\n 200:\t\t\t\t * created dynamically and are initialized with header\n 201:\t\t\t\t * of current packet(except length). But in case of\n 202:\t\t\t\t * SOCK_SEQPACKET, we also must clear message delimeter\n 203:\t\t\t\t * bit (VIRTIO_VSOCK_SEQ_EOM) and MSG_EOR bit\n 204:\t\t\t\t * (VIRTIO_VSOCK_SEQ_EOR) if set. Otherwise,\n 205:\t\t\t\t * there will be sequence of packets with these\n 206:\t\t\t\t * bits set. After initialized header will be copied to\n 207:\t\t\t\t * rx buffer, these required bits will be restored.\n 208:\t\t\t\t */\n 209:\t\t\t\tif (le32_to_cpu(hdr-\u003eflags) \u0026 VIRTIO_VSOCK_SEQ_EOM) {\n 210:\t\t\t\t\thdr-\u003eflags \u0026= ~cpu_to_le32(VIRTIO_VSOCK_SEQ_EOM);\n 211:\t\t\t\t\tflags_to_restore |= VIRTIO_VSOCK_SEQ_EOM;\n 212:\t\n 213:\t\t\t\t\tif (le32_to_cpu(hdr-\u003eflags) \u0026 VIRTIO_VSOCK_SEQ_EOR) {\n 214:\t\t\t\t\t\thdr-\u003eflags \u0026= ~cpu_to_le32(VIRTIO_VSOCK_SEQ_EOR);\n 215:\t\t\t\t\t\tflags_to_restore |= VIRTIO_VSOCK_SEQ_EOR;\n 216:\t\t\t\t\t}\n 217:\t\t\t\t}\n 218:\t\t\t}\n 219:\t\n 220:\t\t\t/* Set the correct length in the header */\n 221:\t\t\thdr-\u003elen = cpu_to_le32(payload_len);\n 222:\t\n 223:\t\t\tnbytes = copy_to_iter(hdr, sizeof(*hdr), \u0026iov_iter);\n 224:\t\t\tif (nbytes != sizeof(*hdr)) {\n"
}

67/3 2026/10/29 04:48 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 75944
							output: 37
							thoughts: 190
						

Thoughts:
**Analyzing the vhost-vsock TX Path**

Okay, so I'm digging into the vhost-vsock transmit path. The core logic seems pretty straightforward at first glance. If the `payload_len` is too large for the available I/O vector space when dealing with `DGRAM` sockets, we just drop the `skb` – a common error handling strategy. Otherwise, we set the header length, ensuring it's in little-endian format with `cpu_to_le32`, and copy the header itself to the I/O iterator. Then, the payload is copied using `skb_copy_datagram_iter`. Finally, the `vhost_add_used` function is called to signal the completion of the transfer, updating the virtqueue with the total bytes written (header size plus payload size). Seems like a standard transmit setup, pretty efficient.

Now, I want to see how the device-side kicks are handled, so the interesting part for me is the `vhost_vsock_handle_tx_kick` function, and I'll jump straight to `drivers/vhost/vsock.c` to look at the code between lines 550 and 630. I'll focus on how the kick triggers the vhost process from the backend. Hopefully, it shows me the synchronization points and potential bottlenecks in the data path. Let's see how they manage the flow and potential contention.



68/3 2026/10/29 04:48 tool read-file
0m Args:
{
  "File": "drivers/vhost/vsock.c",
  "FirstLine": 550,
  "LineCount": 80
}

Results:
{
  "Contents": " 550:\tstatic void vhost_vsock_handle_tx_kick(struct vhost_work *work)\n 551:\t{\n 552:\t\tstruct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue,\n 553:\t\t\t\t\t\t\t  poll.work);\n 554:\t\tstruct vhost_vsock *vsock = container_of(vq-\u003edev, struct vhost_vsock,\n 555:\t\t\t\t\t\t\t dev);\n 556:\t\tint head, pkts = 0, total_len = 0;\n 557:\t\tunsigned int out, in;\n 558:\t\tstruct sk_buff *skb;\n 559:\t\tbool added = false;\n 560:\t\n 561:\t\tmutex_lock(\u0026vq-\u003emutex);\n 562:\t\n 563:\t\tif (!vhost_vq_get_backend(vq))\n 564:\t\t\tgoto out;\n 565:\t\n 566:\t\tif (!vq_meta_prefetch(vq))\n 567:\t\t\tgoto out;\n 568:\t\n 569:\t\tvhost_disable_notify(\u0026vsock-\u003edev, vq);\n 570:\t\tdo {\n 571:\t\t\tstruct virtio_vsock_hdr *hdr;\n 572:\t\n 573:\t\t\tif (!vhost_vsock_more_replies(vsock)) {\n 574:\t\t\t\t/* Stop tx until the device processes already\n 575:\t\t\t\t * pending replies.  Leave tx virtqueue\n 576:\t\t\t\t * callbacks disabled.\n 577:\t\t\t\t */\n 578:\t\t\t\tgoto no_more_replies;\n 579:\t\t\t}\n 580:\t\n 581:\t\t\thead = vhost_get_vq_desc(vq, vq-\u003eiov, ARRAY_SIZE(vq-\u003eiov),\n 582:\t\t\t\t\t\t \u0026out, \u0026in, NULL, NULL);\n 583:\t\t\tif (head \u003c 0)\n 584:\t\t\t\tbreak;\n 585:\t\n 586:\t\t\tif (head == vq-\u003enum) {\n 587:\t\t\t\tif (unlikely(vhost_enable_notify(\u0026vsock-\u003edev, vq))) {\n 588:\t\t\t\t\tvhost_disable_notify(\u0026vsock-\u003edev, vq);\n 589:\t\t\t\t\tcontinue;\n 590:\t\t\t\t}\n 591:\t\t\t\tbreak;\n 592:\t\t\t}\n 593:\t\n 594:\t\t\tskb = vhost_vsock_alloc_skb(vq, out, in);\n 595:\t\t\tif (!skb) {\n 596:\t\t\t\tvq_err(vq, \"Faulted on pkt\\n\");\n 597:\t\t\t\tcontinue;\n 598:\t\t\t}\n 599:\t\n 600:\t\t\ttotal_len += sizeof(*hdr) + skb-\u003elen;\n 601:\t\n 602:\t\t\t/* Deliver to monitoring devices all received packets */\n 603:\t\t\tvirtio_transport_deliver_tap_pkt(skb);\n 604:\t\n 605:\t\t\thdr = virtio_vsock_hdr(skb);\n 606:\t\n 607:\t\t\t/* Only accept correctly addressed packets */\n 608:\t\t\tif (le64_to_cpu(hdr-\u003esrc_cid) == vsock-\u003eguest_cid \u0026\u0026\n 609:\t\t\t    le64_to_cpu(hdr-\u003edst_cid) ==\n 610:\t\t\t    vhost_transport_get_local_cid())\n 611:\t\t\t\tvirtio_transport_recv_pkt(\u0026vhost_transport, skb,\n 612:\t\t\t\t\t\t\t  vsock-\u003enet);\n 613:\t\t\telse\n 614:\t\t\t\tkfree_skb(skb);\n 615:\t\n 616:\t\t\tvhost_add_used(vq, head, 0);\n 617:\t\t\tadded = true;\n 618:\t\t} while(likely(!vhost_exceeds_weight(vq, ++pkts, total_len)));\n 619:\t\n 620:\tno_more_replies:\n 621:\t\tif (added)\n 622:\t\t\tvhost_signal(\u0026vsock-\u003edev, vq);\n 623:\t\n 624:\tout:\n 625:\t\tmutex_unlock(\u0026vq-\u003emutex);\n 626:\t}\n 627:\t\n 628:\tstatic void vhost_vsock_handle_rx_kick(struct vhost_work *work)\n 629:\t{\n"
}

69/3 2026/10/29 04:48 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 77658
							output: 37
							thoughts: 44
						

Thoughts:
Here's my analysis of what I'm looking at in the `vhost_vsock_alloc_skb` function within the `drivers/vhost/vsock.c` file:

**Deconstructing `vhost_vsock_alloc_skb`**

Okay, so I'm diving into the `vhost_vsock_alloc_skb` function. I've got my eye on lines 370 through 420 in `drivers/vhost/vsock.c`. I need to figure out how this function allocates SKBs specifically for vhost-vsock. I'm going to quickly parse the relevant code to see the key elements of the allocation process. What are the specific considerations for vsock traffic? Is it allocating a standard skb, or are there any special vhost or vsock flags, optimizations, or data structures being used here? I'm anticipating some level of interaction with the virtio queues and potentially device-specific memory allocation. Let's see how they handle the buffer sizes, and any potential pre-allocation or caching strategies. This should be interesting.



70/3 2026/10/29 04:48 tool read-file
0m Args:
{
  "File": "drivers/vhost/vsock.c",
  "FirstLine": 370,
  "LineCount": 50
}

Results:
{
  "Contents": " 370:\tvhost_vsock_alloc_skb(struct vhost_virtqueue *vq,\n 371:\t\t\t      unsigned int out, unsigned int in)\n 372:\t{\n 373:\t\tstruct virtio_vsock_hdr *hdr;\n 374:\t\tstruct iov_iter iov_iter;\n 375:\t\tstruct sk_buff *skb;\n 376:\t\tsize_t payload_len;\n 377:\t\tsize_t nbytes;\n 378:\t\tsize_t len;\n 379:\t\n 380:\t\tif (in != 0) {\n 381:\t\t\tvq_err(vq, \"Expected 0 input buffers, got %u\\n\", in);\n 382:\t\t\treturn NULL;\n 383:\t\t}\n 384:\t\n 385:\t\tlen = iov_length(vq-\u003eiov, out);\n 386:\t\n 387:\t\tif (len \u003c VIRTIO_VSOCK_SKB_HEADROOM ||\n 388:\t\t    len \u003e VIRTIO_VSOCK_MAX_PKT_BUF_SIZE + VIRTIO_VSOCK_SKB_HEADROOM)\n 389:\t\t\treturn NULL;\n 390:\t\n 391:\t\t/* len contains both payload and hdr */\n 392:\t\tskb = virtio_vsock_alloc_skb(len, GFP_KERNEL);\n 393:\t\tif (!skb)\n 394:\t\t\treturn NULL;\n 395:\t\n 396:\t\tiov_iter_init(\u0026iov_iter, ITER_SOURCE, vq-\u003eiov, out, len);\n 397:\t\n 398:\t\thdr = virtio_vsock_hdr(skb);\n 399:\t\tnbytes = copy_from_iter(hdr, sizeof(*hdr), \u0026iov_iter);\n 400:\t\tif (nbytes != sizeof(*hdr)) {\n 401:\t\t\tvq_err(vq, \"Expected %zu bytes for pkt-\u003ehdr, got %zu bytes\\n\",\n 402:\t\t\t       sizeof(*hdr), nbytes);\n 403:\t\t\tkfree_skb(skb);\n 404:\t\t\treturn NULL;\n 405:\t\t}\n 406:\t\n 407:\t\tpayload_len = le32_to_cpu(hdr-\u003elen);\n 408:\t\n 409:\t\t/* No payload */\n 410:\t\tif (!payload_len)\n 411:\t\t\treturn skb;\n 412:\t\n 413:\t\t/* The pkt is too big or the length in the header is invalid */\n 414:\t\tif (payload_len + sizeof(*hdr) \u003e len) {\n 415:\t\t\tkfree_skb(skb);\n 416:\t\t\treturn NULL;\n 417:\t\t}\n 418:\t\n 419:\t\tvirtio_vsock_skb_put(skb, payload_len);\n"
}

71/3 2026/10/29 04:48 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 78751
							output: 37
							thoughts: 30
						

Thoughts:
Okay, let's dive into this `vhost_vsock_alloc_skb` function further. I need to understand its nuances.

**Diving Deeper into vhost_vsock_alloc_skb**

Right, so I'm picking up from lines 420-435 within the `vhost_vsock_alloc_skb` function. I'm keen to see what's happening within this specific section, how it fits into the broader allocation strategy, and what crucial decisions are being made. I'll need to dissect these lines carefully. I'm particularly interested in seeing how the function handles any potential errors. I'll be looking for memory allocation patterns and checking to see if there are any specific conditions that trigger the choice of data structure.



72/3 2026/10/29 04:48 tool read-file
0m Args:
{
  "File": "drivers/vhost/vsock.c",
  "FirstLine": 420,
  "LineCount": 20
}

Results:
{
  "Contents": " 420:\t\n 421:\t\tif (skb_copy_datagram_from_iter(skb, 0, \u0026iov_iter, payload_len)) {\n 422:\t\t\tvq_err(vq, \"Failed to copy %zu byte payload\\n\", payload_len);\n 423:\t\t\tkfree_skb(skb);\n 424:\t\t\treturn NULL;\n 425:\t\t}\n 426:\t\n 427:\t\treturn skb;\n 428:\t}\n 429:\t\n 430:\t/* Is there space left for replies to rx packets? */\n 431:\tstatic bool vhost_vsock_more_replies(struct vhost_vsock *vsock)\n 432:\t{\n 433:\t\tstruct vhost_virtqueue *vq = \u0026vsock-\u003evqs[VSOCK_VQ_TX];\n 434:\t\tint val;\n 435:\t\n 436:\t\tsmp_rmb(); /* paired with atomic_inc() and atomic_dec_return() */\n 437:\t\tval = atomic_read(\u0026vsock-\u003equeued_replies);\n 438:\t\n 439:\t\treturn val \u003c vq-\u003enum;\n"
}

73/3 2026/10/29 04:48 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 79313
							output: 17
							thoughts: 1452
						

Thoughts:
**Diving Deep into vsock Datagram Handling**

Okay, so I'm digging into how `vhost_vsock_alloc_skb` handles payload copies, specifically looking at `skb_copy_datagram_from_iter`. I'm trying to understand the nuances of the datagram flow and how the transport layer interacts with it, so I need to understand `virtio_transport_dgram_source_allow`.

I see that `virtio_transport_dgram_source_allow` is called. The important bit here is the `vsock_dgram_get_transport` call and the subsequent comparison involving `vsk->remote_addr`. It appears the source validation hinges on the remote address and the transport used. `vsk->remote_addr` is set during connection establishment in `vsock_dgram_connect`, copied from the provided `remote_addr` which is initialized to `VMADDR_CID_ANY` and `VMADDR_PORT_ANY` on socket creation and reset on disconnect. The critical piece is that the transport type needs to match.

Looking at `vsock_dgram_get_transport`, I'm confirming that the variables are properly initialized to avoid unexpected behaviors. Now, examining `vsock_assign_transport` I am ensuring that the transport is properly assigned in `SOCK_DGRAM`.

Next up, I need to check how `to_host` is used in `virtio_vsock_skb_cb`. It seems this is read in a couple of places. The first place is `virtio_transport_dgram_dequeue`, where it sets the `VMADDR_FLAG_TO_HOST` in the sockaddr if `to_host` is true. The second one, in `virtio_transport_recv_pkt`, sets the `to_host` flag based on the local CID. I need to grep and check if `to_host` is read anywhere else to make sure I am accounting for everything.



74/3 2026/10/29 04:48 tool grepper
0m Args:
{
  "Expression": "to_host"
}

Results:
{
  "Output": "\nFull output is too long, showing 200 out of 3276 lines.\nUse more precise expression or PathPrefixes if possible.\n\nNumber of matching lines per file (209 files in total):\nDocumentation/sound/designs/midi-2.0.rst:1\nDocumentation/usb/gadget-testing.rst:3\narch/arm64/include/asm/kvm_emulate.h:1\narch/arm64/include/asm/kvm_hyp.h:1\narch/arm64/kvm/fpsimd.c:1\narch/arm64/kvm/hyp/include/hyp/debug-sr.h:1\narch/arm64/kvm/hyp/include/hyp/switch.h:1\narch/arm64/kvm/hyp/nvhe/debug-sr.c:4\narch/arm64/kvm/hyp/nvhe/hyp-main.c:1\narch/arm64/kvm/hyp/nvhe/switch.c:4\narch/arm64/kvm/hyp/vhe/debug-sr.c:2\narch/arm64/kvm/hyp/vhe/switch.c:2\narch/arm64/kvm/mmio.c:1\narch/arm64/kvm/vgic/vgic-mmio.c:2\narch/arm64/kvm/vgic/vgic-mmio.h:1\narch/loongarch/kvm/switch.S:2\narch/mips/kvm/entry.c:9\narch/powerpc/include/asm/pci-bridge.h:1\narch/powerpc/kernel/dma-iommu.c:1\narch/powerpc/kernel/isa-bridge.c:1\narch/powerpc/kernel/msi.c:2\narch/powerpc/kernel/pci-common.c:16\narch/powerpc/kernel/pci-hotplug.c:2\narch/powerpc/kernel/pci_64.c:3\narch/powerpc/kernel/pci_of_scan.c:1\narch/powerpc/kvm/book3s_hv.c:1\narch/powerpc/kvm/book3s_hv.h:1\narch/powerpc/kvm/book3s_hv_p9_entry.c:4\narch/powerpc/kvm/book3s_hv_p9_perf.c:2\narch/powerpc/kvm/book3s_hv_rmhandlers.S:4\narch/powerpc/platforms/44x/pci.c:3\narch/powerpc/platforms/52xx/efika.c:2\narch/powerpc/platforms/52xx/mpc52xx_pci.c:2\narch/powerpc/platforms/chrp/pci.c:4\narch/powerpc/platforms/fsl_uli1575.c:1\narch/powerpc/platforms/pasemi/pci.c:4\narch/powerpc/platforms/powermac/pci.c:6\narch/powerpc/platforms/powernv/eeh-powernv.c:2\narch/powerpc/platforms/powernv/ocxl.c:4\narch/powerpc/platforms/powernv/pci-ioda.c:1\narch/powerpc/platforms/pseries/pci.c:1\narch/powerpc/sysdev/fsl_msi.c:2\narch/powerpc/sysdev/fsl_pci.c:6\narch/powerpc/sysdev/indirect_pci.c:2\narch/powerpc/sysdev/mpic_u3msi.c:1\narch/powerpc/sysdev/tsi108_pci.c:2\narch/x86/kvm/vmx/tdx.c:2\narch/x86/kvm/vmx/vmx.c:4\ndrivers/accel/habanalabs/common/firmware_if.c:2\ndrivers/accel/habanalabs/common/habanalabs.h:2\n... and 159 more files\n\nDocumentation/sound/designs/midi-2.0.rst=494=You can play a MIDI file on the gadget side::\nDocumentation/sound/designs/midi-2.0.rst-495-\nDocumentation/sound/designs/midi-2.0.rst:496:  % aplaymidi -p 20:1 to_host.mid\nDocumentation/sound/designs/midi-2.0.rst-497-\n--\nDocumentation/usb/gadget-testing.rst=397=gadget::\n--\nDocumentation/usb/gadget-testing.rst-402-\nDocumentation/usb/gadget-testing.rst:403:  $ aplaymidi -p 20:0 to_host.mid\nDocumentation/usb/gadget-testing.rst-404-\n--\nDocumentation/usb/gadget-testing.rst=415=host::\n--\nDocumentation/usb/gadget-testing.rst-423-\nDocumentation/usb/gadget-testing.rst:424:The from_gadget.mid should sound identical to the to_host.mid.\nDocumentation/usb/gadget-testing.rst-425-\n--\nDocumentation/usb/gadget-testing.rst=1099=On the gadget::\n--\nDocumentation/usb/gadget-testing.rst-1105-      1 'Group 1 (MIDI 2.0 Gadget I/O)'\nDocumentation/usb/gadget-testing.rst:1106:  $ aplaymidi -p 20:1 to_host.mid\nDocumentation/usb/gadget-testing.rst-1107-\n--\narch/arm64/include/asm/kvm_emulate.h=575=static inline bool kvm_vcpu_is_be(struct kvm_vcpu *vcpu)\n--\narch/arm64/include/asm/kvm_emulate.h-589-#if ARM64_S390_COMMON == 1\narch/arm64/include/asm/kvm_emulate.h:590:static inline unsigned long vcpu_data_guest_to_host(struct kvm_vcpu *vcpu,\narch/arm64/include/asm/kvm_emulate.h-591-\t\t\t\t\t\t    unsigned long data,\n--\narch/arm64/include/asm/kvm_hyp.h=120=void __debug_switch_to_guest(struct kvm_vcpu *vcpu);\narch/arm64/include/asm/kvm_hyp.h:121:void __debug_switch_to_host(struct kvm_vcpu *vcpu);\narch/arm64/include/asm/kvm_hyp.h-122-\n--\narch/arm64/kvm/fpsimd.c=115=void kvm_arch_vcpu_put_fp(struct kvm_vcpu *vcpu)\n--\narch/arm64/kvm/fpsimd.c-120-\t * See comment in kvm_arch_vcpu_load_fp(). Note that we also rely on\narch/arm64/kvm/fpsimd.c:121:\t * the guest's max VL to have been set by fpsimd_lazy_switch_to_host()\narch/arm64/kvm/fpsimd.c-122-\t * so that any intervening kernel-mode SIMD (NEON or otherwise)\n--\narch/arm64/kvm/hyp/include/hyp/debug-sr.h=134=static inline void __debug_switch_to_guest_common(struct kvm_vcpu *vcpu)\n--\narch/arm64/kvm/hyp/include/hyp/debug-sr.h-152-\narch/arm64/kvm/hyp/include/hyp/debug-sr.h:153:static inline void __debug_switch_to_host_common(struct kvm_vcpu *vcpu)\narch/arm64/kvm/hyp/include/hyp/debug-sr.h-154-{\n--\narch/arm64/kvm/hyp/include/hyp/switch.h=523=static inline void fpsimd_lazy_switch_to_guest(struct kvm_vcpu *vcpu)\n--\narch/arm64/kvm/hyp/include/hyp/switch.h-543-\narch/arm64/kvm/hyp/include/hyp/switch.h:544:static inline void fpsimd_lazy_switch_to_host(struct kvm_vcpu *vcpu)\narch/arm64/kvm/hyp/include/hyp/switch.h-545-{\n--\narch/arm64/kvm/hyp/nvhe/debug-sr.c=127=static void __trace_switch_to_guest(void)\n--\narch/arm64/kvm/hyp/nvhe/debug-sr.c-137-\narch/arm64/kvm/hyp/nvhe/debug-sr.c:138:static void __trace_switch_to_host(void)\narch/arm64/kvm/hyp/nvhe/debug-sr.c-139-{\n--\narch/arm64/kvm/hyp/nvhe/debug-sr.c=208=void __debug_restore_host_buffers_nvhe(struct kvm_vcpu *vcpu)\n--\narch/arm64/kvm/hyp/nvhe/debug-sr.c-214-\tif (__trace_needs_switch())\narch/arm64/kvm/hyp/nvhe/debug-sr.c:215:\t\t__trace_switch_to_host();\narch/arm64/kvm/hyp/nvhe/debug-sr.c-216-}\narch/arm64/kvm/hyp/nvhe/debug-sr.c-217-\narch/arm64/kvm/hyp/nvhe/debug-sr.c:218:void __debug_switch_to_host(struct kvm_vcpu *vcpu)\narch/arm64/kvm/hyp/nvhe/debug-sr.c-219-{\narch/arm64/kvm/hyp/nvhe/debug-sr.c:220:\t__debug_switch_to_host_common(vcpu);\narch/arm64/kvm/hyp/nvhe/debug-sr.c-221-}\n--\narch/arm64/kvm/hyp/nvhe/hyp-main.c=907=DEFINE_KVM_HOST_HCALL(int, __kvm_vcpu_run, struct kvm_vcpu __kern *, vcpu)\n--\narch/arm64/kvm/hyp/nvhe/hyp-main.c-951-\t\tret = __kvm_vcpu_run(host_vcpu);\narch/arm64/kvm/hyp/nvhe/hyp-main.c:952:\t\tfpsimd_lazy_switch_to_host(host_vcpu);\narch/arm64/kvm/hyp/nvhe/hyp-main.c-953-\t}\n--\narch/arm64/kvm/hyp/nvhe/switch.c=147=static bool __pmu_switch_to_guest(struct kvm_vcpu *vcpu)\n--\narch/arm64/kvm/hyp/nvhe/switch.c-162- */\narch/arm64/kvm/hyp/nvhe/switch.c:163:static void __pmu_switch_to_host(struct kvm_vcpu *vcpu)\narch/arm64/kvm/hyp/nvhe/switch.c-164-{\n--\narch/arm64/kvm/hyp/nvhe/switch.c-174-#define __pmu_switch_to_guest(v)\t({ false; })\narch/arm64/kvm/hyp/nvhe/switch.c:175:#define __pmu_switch_to_host(v)\t\tdo {} while (0)\narch/arm64/kvm/hyp/nvhe/switch.c-176-#endif\n--\narch/arm64/kvm/hyp/nvhe/switch.c=279=int __kvm_vcpu_run(struct kvm_vcpu *vcpu)\n--\narch/arm64/kvm/hyp/nvhe/switch.c-376-\narch/arm64/kvm/hyp/nvhe/switch.c:377:\t__debug_switch_to_host(vcpu);\narch/arm64/kvm/hyp/nvhe/switch.c-378-\t/*\n--\narch/arm64/kvm/hyp/nvhe/switch.c-384-\tif (pmu_switch_needed)\narch/arm64/kvm/hyp/nvhe/switch.c:385:\t\t__pmu_switch_to_host(vcpu);\narch/arm64/kvm/hyp/nvhe/switch.c-386-\n--\narch/arm64/kvm/hyp/vhe/debug-sr.c=13=void __debug_switch_to_guest(struct kvm_vcpu *vcpu)\n--\narch/arm64/kvm/hyp/vhe/debug-sr.c-17-\narch/arm64/kvm/hyp/vhe/debug-sr.c:18:void __debug_switch_to_host(struct kvm_vcpu *vcpu)\narch/arm64/kvm/hyp/vhe/debug-sr.c-19-{\narch/arm64/kvm/hyp/vhe/debug-sr.c:20:\t__debug_switch_to_host_common(vcpu);\narch/arm64/kvm/hyp/vhe/debug-sr.c-21-}\n--\narch/arm64/kvm/hyp/vhe/switch.c=609=static int __kvm_vcpu_run_vhe(struct kvm_vcpu *vcpu)\n--\narch/arm64/kvm/hyp/vhe/switch.c-647-\narch/arm64/kvm/hyp/vhe/switch.c:648:\t__debug_switch_to_host(vcpu);\narch/arm64/kvm/hyp/vhe/switch.c-649-\n--\narch/arm64/kvm/hyp/vhe/switch.c-657-\narch/arm64/kvm/hyp/vhe/switch.c:658:\tfpsimd_lazy_switch_to_host(vcpu);\narch/arm64/kvm/hyp/vhe/switch.c-659-\n--\narch/arm64/kvm/mmio.c=156=int io_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa)\n--\narch/arm64/kvm/mmio.c-225-\tif (is_write) {\narch/arm64/kvm/mmio.c:226:\t\tdata = vcpu_data_guest_to_host(vcpu, vcpu_get_reg(vcpu, rt),\narch/arm64/kvm/mmio.c-227-\t\t\t\t\t       len);\n--\narch/arm64/kvm/vgic/vgic-mmio.c=865=void vgic_get_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr)\n--\narch/arm64/kvm/vgic/vgic-mmio.c-895- */\narch/arm64/kvm/vgic/vgic-mmio.c:896:unsigned long vgic_data_mmio_bus_to_host(const void *val, unsigned int len)\narch/arm64/kvm/vgic/vgic-mmio.c-897-{\n--\narch/arm64/kvm/vgic/vgic-mmio.c=1077=static int dispatch_mmio_write(struct kvm_vcpu *vcpu, struct kvm_io_device *dev,\n--\narch/arm64/kvm/vgic/vgic-mmio.c-1081-\tconst struct vgic_register_region *region;\narch/arm64/kvm/vgic/vgic-mmio.c:1082:\tunsigned long data = vgic_data_mmio_bus_to_host(val, len);\narch/arm64/kvm/vgic/vgic-mmio.c-1083-\n--\narch/arm64/kvm/vgic/vgic-mmio.h=37=extern const struct kvm_io_device_ops kvm_io_gic_ops;\n--\narch/arm64/kvm/vgic/vgic-mmio.h-100-\narch/arm64/kvm/vgic/vgic-mmio.h:101:unsigned long vgic_data_mmio_bus_to_host(const void *val, unsigned int len);\narch/arm64/kvm/vgic/vgic-mmio.h-102-\n--\narch/loongarch/kvm/switch.S=112=SYM_CODE_START(kvm_exc_entry)\n--\narch/loongarch/kvm/switch.S-180-\t/* Resume host when ret \u003c= 0 */\narch/loongarch/kvm/switch.S:181:\tblez\ta0, ret_to_host\narch/loongarch/kvm/switch.S-182-\n--\narch/loongarch/kvm/switch.S-192-\narch/loongarch/kvm/switch.S:193:ret_to_host:\narch/loongarch/kvm/switch.S-194-\tld.d    a2, a2, KVM_ARCH_HSP\n--\narch/mips/kvm/entry.c=29=enum label_id {\n--\narch/mips/kvm/entry.c-31-\tlabel_msa_1,\narch/mips/kvm/entry.c:32:\tlabel_return_to_host,\narch/mips/kvm/entry.c-33-\tlabel_kernel_asid,\n--\narch/mips/kvm/entry.c=38=UASM_L_LA(_msa_1)\narch/mips/kvm/entry.c:39:UASM_L_LA(_return_to_host)\narch/mips/kvm/entry.c-40-UASM_L_LA(_kernel_asid)\n--\narch/mips/kvm/entry.c=45=static void *kvm_mips_build_ret_to_guest(void *addr);\narch/mips/kvm/entry.c:46:static void *kvm_mips_build_ret_to_host(void *addr);\narch/mips/kvm/entry.c-47-\n--\narch/mips/kvm/entry.c=131=static inline void build_set_exc_base(u32 **p, unsigned int reg)\n--\narch/mips/kvm/entry.c-151- * The exit from the guest and return to the caller is handled by the code\narch/mips/kvm/entry.c:152: * generated by kvm_mips_build_ret_to_host().\narch/mips/kvm/entry.c-153- *\n--\narch/mips/kvm/entry.c=737=static void *kvm_mips_build_ret_from_exit(void *addr)\n--\narch/mips/kvm/entry.c-765-\tuasm_i_andi(\u0026p, GPR_T0, GPR_V0, RESUME_HOST);\narch/mips/kvm/entry.c:766:\tuasm_il_bnez(\u0026p, \u0026r, GPR_T0, label_return_to_host);\narch/mips/kvm/entry.c-767-\t uasm_i_nop(\u0026p);\n--\narch/mips/kvm/entry.c-770-\narch/mips/kvm/entry.c:771:\tuasm_l_return_to_host(\u0026l, p);\narch/mips/kvm/entry.c:772:\tp = kvm_mips_build_ret_to_host(p);\narch/mips/kvm/entry.c-773-\n--\narch/mips/kvm/entry.c=788=static void *kvm_mips_build_ret_to_guest(void *addr)\n--\narch/mips/kvm/entry.c-818-/**\narch/mips/kvm/entry.c:819: * kvm_mips_build_ret_to_host() - Assemble code to return to the host.\narch/mips/kvm/entry.c-820- * @addr:\tAddress to start writing code.\n--\narch/mips/kvm/entry.c-827- */\narch/mips/kvm/entry.c:828:static void *kvm_mips_build_ret_to_host(void *addr)\narch/mips/kvm/entry.c-829-{\n--\narch/powerpc/include/asm/pci-bridge.h=170=extern int indirect_write_config(struct pci_bus *bus, unsigned int devfn,\n--\narch/powerpc/include/asm/pci-bridge.h-172-\narch/powerpc/include/asm/pci-bridge.h:173:static inline struct pci_controller *pci_bus_to_host(const struct pci_bus *bus)\narch/powerpc/include/asm/pci-bridge.h-174-{\n--\narch/powerpc/kernel/dma-iommu.c=147=static bool dma_iommu_bypass_supported(struct device *dev, u64 mask)\n--\narch/powerpc/kernel/dma-iommu.c-149-\tstruct pci_dev *pdev = to_pci_dev(dev);\narch/powerpc/kernel/dma-iommu.c:150:\tstruct pci_controller *phb = pci_bus_to_host(pdev-\u003ebus);\narch/powerpc/kernel/dma-iommu.c-151-\n--\narch/powerpc/kernel/isa-bridge.c=184=static void isa_bridge_find_late(struct pci_dev *pdev,\n\n"
}

75/3 2026/10/29 04:48 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 86869
							output: 54
							thoughts: 24
						

Thoughts:
Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:

**Focusing the Search Scope**

Right, I need to narrow down my search parameters. Given the breadth of the codebase, a blanket `grep` would be overwhelming and produce a torrent of irrelevant results. I need to be strategic. My initial instinct is to limit my scope to specific areas where I believe the relevant functionality or issue might reside. Considering my understanding of the architecture, I'm thinking I should start by concentrating my `grep` searches specifically on two key locations. Those would be the `net/vmw_vsock/` directory, given its likely involvement in networking and virtualization, and the `drivers/vhost/` directory, because of its likely association with virtual host functionality. Restricting the initial search to these two directories is the logical first step to improve the signal-to-noise ratio and allow for a more focused and efficient investigation.



76/3 2026/10/29 04:48 tool grepper
0m Args:
{
  "Expression": "to_host",
  "PathPrefixes": [
    "net/vmw_vsock/",
    "drivers/vhost/",
    "include/linux/virtio_vsock.h"
  ]
}

Results:
{
  "Output": "include/linux/virtio_vsock.h=12=struct virtio_vsock_skb_cb {\n--\ninclude/linux/virtio_vsock.h-16-\tbool tx_accounted;\ninclude/linux/virtio_vsock.h:17:\tbool to_host;\ninclude/linux/virtio_vsock.h-18-\tu32 offset;\n--\nnet/vmw_vsock/af_vsock.c=747=vsock_dgram_get_transport(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/af_vsock.c-754-\tu32 local_cid;\nnet/vmw_vsock/af_vsock.c:755:\tbool to_host = false;\nnet/vmw_vsock/af_vsock.c-756-\tint err = -ENODEV;\n--\nnet/vmw_vsock/af_vsock.c-776-\t\ttransport = transport_g2h;\nnet/vmw_vsock/af_vsock.c:777:\t\tto_host = true;\nnet/vmw_vsock/af_vsock.c-778-\t} else {\n--\nnet/vmw_vsock/af_vsock.c-812-\t}\nnet/vmw_vsock/af_vsock.c:813:\tif (to_host)\nnet/vmw_vsock/af_vsock.c-814-\t\tremote_addr-\u003esvm_flags |= VMADDR_FLAG_TO_HOST;\n--\nnet/vmw_vsock/virtio_transport_common.c=1039=virtio_transport_dgram_dequeue(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-1073-\t\tmsg-\u003emsg_namelen = sizeof(*vm_addr);\nnet/vmw_vsock/virtio_transport_common.c:1074:\t\tif (VIRTIO_VSOCK_SKB_CB(skb)-\u003eto_host)\nnet/vmw_vsock/virtio_transport_common.c-1075-\t\t\tvm_addr-\u003esvm_flags = VMADDR_FLAG_TO_HOST;\n--\nnet/vmw_vsock/virtio_transport_common.c=1910=void virtio_transport_recv_pkt(struct virtio_transport *t,\n--\nnet/vmw_vsock/virtio_transport_common.c-1962-\nnet/vmw_vsock/virtio_transport_common.c:1963:\t\tVIRTIO_VSOCK_SKB_CB(skb)-\u003eto_host =\nnet/vmw_vsock/virtio_transport_common.c-1964-\t\t\tt-\u003etransport.get_local_cid() \u003e VMADDR_CID_HOST;\n"
}

77/3 2026/10/29 04:48 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 87880
							output: 34
							thoughts: 284
						

Thoughts:
**Investigating the Data Flow in Virtio-VSOCK for DGRAM Sockets**

Okay, so I'm digging into this `to_host` flag within the Virtio-VSOCK driver. It looks like `to_host` is written within `virtio_transport_recv_pkt`, specifically when an SKB (Socket Buffer) is received. The key line is:

```c
VIRTIO_VSOCK_SKB_CB(skb)->to_host = t->transport.get_local_cid() > VMADDR_CID_HOST;
```

It appears to be set based on the local CID (Connection ID). Then, later on, this `to_host` flag is read within `virtio_transport_dgram_dequeue`:

```c
if (VIRTIO_VSOCK_SKB_CB(skb)->to_host)
	vm_addr->svm_flags = VMADDR_FLAG_TO_HOST;
```

Here's where it gets interesting and I need to clarify a potential assumption. My immediate question is: *Can an SKB actually arrive at `virtio_transport_dgram_dequeue` without having been processed by `virtio_transport_recv_pkt` first?*

This is the critical point. How exactly is an SKB, intended for a `SOCK_DGRAM` socket, placed onto the socket's receive queue (`sk->sk_receive_queue`) in the first place? I need to trace this data path. I need to be sure that the `to_host` flag is consistently and correctly set.

Therefore, I need to check where `sk->sk_receive_queue` is actually being queued for vsock. The most effective way to understand the flow will be to find where this queue is modified and ensure that the packet flow matches the conditions. A quick `grep` for `sk_receive_queue` within the `net/vmw_vsock/` directory should reveal the relevant code paths. I'll start there.



78/3 2026/10/29 04:49 tool grepper
0m Args:
{
  "Expression": "sk_receive_queue",
  "PathPrefixes": [
    "net/vmw_vsock/"
  ]
}

Results:
{
  "Output": "net/vmw_vsock/af_vsock.c=1131=static void __vsock_release(struct sock *sk, int level)\n--\nnet/vmw_vsock/af_vsock.c-1160-\nnet/vmw_vsock/af_vsock.c:1161:\tskb_queue_purge(\u0026sk-\u003esk_receive_queue);\nnet/vmw_vsock/af_vsock.c-1162-\n--\nnet/vmw_vsock/af_vsock.c=1173=static void vsock_sk_destruct(struct sock *sk)\n--\nnet/vmw_vsock/af_vsock.c-1177-\t/* Socket buffers may remain in the queue in VMCI datagram mode */\nnet/vmw_vsock/af_vsock.c:1178:\t__skb_queue_purge(\u0026sk-\u003esk_receive_queue);\nnet/vmw_vsock/af_vsock.c-1179-\t/* Flush MSG_ZEROCOPY leftovers. */\n--\nnet/vmw_vsock/af_vsock.c=1430=static __poll_t vsock_poll(struct file *file, struct socket *sock,\n--\nnet/vmw_vsock/af_vsock.c-1461-\t\t */\nnet/vmw_vsock/af_vsock.c:1462:\t\tif (!skb_queue_empty_lockless(\u0026sk-\u003esk_receive_queue) ||\nnet/vmw_vsock/af_vsock.c-1463-\t\t    (sk-\u003esk_shutdown \u0026 RCV_SHUTDOWN)) {\n--\nnet/vmw_vsock/af_vsock.c=1721=static int vsock_do_ioctl(struct socket *sock, unsigned int cmd,\n--\nnet/vmw_vsock/af_vsock.c-1747-\nnet/vmw_vsock/af_vsock.c:1748:\t\t\tspin_lock_bh(\u0026sk-\u003esk_receive_queue.lock);\nnet/vmw_vsock/af_vsock.c:1749:\t\t\tskb = skb_peek(\u0026sk-\u003esk_receive_queue);\nnet/vmw_vsock/af_vsock.c-1750-\t\t\tif (skb \u0026\u0026 vsk-\u003etransport-\u003edgram_payload_len)\n--\nnet/vmw_vsock/af_vsock.c-1753-\t\t\t\tn_bytes = skb ? skb-\u003elen : 0;\nnet/vmw_vsock/af_vsock.c:1754:\t\t\tspin_unlock_bh(\u0026sk-\u003esk_receive_queue.lock);\nnet/vmw_vsock/af_vsock.c-1755-\t\t} else {\n--\nnet/vmw_vsock/vsock_bpf.c-15-#define vsock_sk_has_data(__sk, __psock)\t\t\t\t\\\nnet/vmw_vsock/vsock_bpf.c:16:\t\t({\t!skb_queue_empty(\u0026(__sk)-\u003esk_receive_queue) ||\t\\\nnet/vmw_vsock/vsock_bpf.c-17-\t\t\t!skb_queue_empty(\u0026(__psock)-\u003eingress_skb) ||\t\\\n"
}

79/3 2026/10/29 04:49 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 89464
							output: 38
							thoughts: 28
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:

**Analyzing `net/vmw_vsock/` for `sock_queue_rcv_skb`**

Alright, I'm diving back into this `net/vmw_vsock/` code. I'm thinking, in my typical diagnostic fashion, it's time to leverage my knowledge of the kernel and look for critical data flow. I should grep for `sock_queue_rcv_skb` within that specific directory. I want to see how that function is being used within the context of the vmw_vsock driver. This likely relates to how incoming socket buffer (skb) data is processed, queued, and ultimately delivered to user-space applications. Finding these call-sites will give me a clear picture of the relevant receive paths. It's the most likely spot to identify potential issues or inefficiencies in the data flow, especially given my recent focus on network performance and socket-level interactions. I'm assuming that function is at the heart of the packet reception process.



80/3 2026/10/29 04:49 tool grepper
0m Args:
{
  "Expression": "sock_queue_rcv_skb",
  "PathPrefixes": [
    "net/vmw_vsock/"
  ]
}

Results:
{
  "Output": "net/vmw_vsock/af_vsock.c=178=static void vsock_sk_destruct(struct sock *sk);\nnet/vmw_vsock/af_vsock.c:179:static int vsock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb);\nnet/vmw_vsock/af_vsock.c-180-static void vsock_close(struct sock *sk, long timeout);\n--\nnet/vmw_vsock/af_vsock.c=1061=static struct sock *__vsock_create(struct net *net,\n--\nnet/vmw_vsock/af_vsock.c-1089-\tsk-\u003esk_destruct = vsock_sk_destruct;\nnet/vmw_vsock/af_vsock.c:1090:\tsk-\u003esk_backlog_rcv = vsock_queue_rcv_skb;\nnet/vmw_vsock/af_vsock.c-1091-\tsock_reset_flag(sk, SOCK_DONE);\n--\nnet/vmw_vsock/af_vsock.c=1173=static void vsock_sk_destruct(struct sock *sk)\n--\nnet/vmw_vsock/af_vsock.c-1192-\nnet/vmw_vsock/af_vsock.c:1193:static int vsock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)\nnet/vmw_vsock/af_vsock.c-1194-{\n--\nnet/vmw_vsock/af_vsock.c-1196-\nnet/vmw_vsock/af_vsock.c:1197:\terr = sock_queue_rcv_skb(sk, skb);\nnet/vmw_vsock/af_vsock.c-1198-\tif (err)\n--\nnet/vmw_vsock/virtio_transport_common.c=1910=void virtio_transport_recv_pkt(struct virtio_transport *t,\n--\nnet/vmw_vsock/virtio_transport_common.c-1964-\t\t\tt-\u003etransport.get_local_cid() \u003e VMADDR_CID_HOST;\nnet/vmw_vsock/virtio_transport_common.c:1965:\t\tif (sock_queue_rcv_skb(sk, skb))\nnet/vmw_vsock/virtio_transport_common.c-1966-\t\t\tkfree_skb(skb);\n"
}

81/3 2026/10/29 04:49 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 90425
							output: 43
							thoughts: 109
						

Thoughts:
Okay, let's dive into this.

**Investigating the Data Flow in vsock and VMCI**

My first instinct is to zero in on `vsock_queue_rcv_skb` within `net/vmw_vsock/af_vsock.c`. I need to understand the call chain here. Who's actually feeding data into this function? Ah, right, `sk->sk_backlog_rcv = vsock_queue_rcv_skb`. So that's the link. Now I understand where incoming packets are being staged.

But the broader picture... isn't VMCI also involved in queuing packets somehow? I need to clarify how datagrams are handled at a lower level. My next step is to examine `vmci_transport_dgram_dequeue` in `net/vmw_vsock/vmci_transport.c`. That should give me the details on how VMCI datagrams are actually being processed. This should help me construct a coherent understanding of the overall data flow, from the kernel to the application.



82/3 2026/10/29 04:49 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/vmci_transport.c",
  "FirstLine": 1770,
  "LineCount": 55
}

Results:
{
  "Contents": "1770:\t\treturn skb-\u003elen - sizeof(struct vmci_datagram);\n1771:\t}\n1772:\t\n1773:\tstatic int vmci_transport_dgram_dequeue(struct vsock_sock *vsk,\n1774:\t\t\t\t\t\tstruct msghdr *msg, size_t len,\n1775:\t\t\t\t\t\tint flags)\n1776:\t{\n1777:\t\tint err;\n1778:\t\tstruct vmci_datagram *dg;\n1779:\t\tsize_t payload_len;\n1780:\t\tstruct sk_buff *skb;\n1781:\t\n1782:\t\tif (flags \u0026 MSG_OOB || flags \u0026 MSG_ERRQUEUE)\n1783:\t\t\treturn -EOPNOTSUPP;\n1784:\t\n1785:\t\t/* Retrieve the head sk_buff from the socket's receive queue. */\n1786:\t\terr = 0;\n1787:\t\tskb = skb_recv_datagram(\u0026vsk-\u003esk, flags, \u0026err);\n1788:\t\tif (!skb)\n1789:\t\t\treturn err;\n1790:\t\n1791:\t\tdg = (struct vmci_datagram *)skb-\u003edata;\n1792:\t\tif (!dg)\n1793:\t\t\t/* err is 0, meaning we read zero bytes. */\n1794:\t\t\tgoto out;\n1795:\t\n1796:\t\tpayload_len = dg-\u003epayload_size;\n1797:\t\t/* Ensure the sk_buff matches the payload size claimed in the packet. */\n1798:\t\tif (payload_len != skb-\u003elen - sizeof(*dg)) {\n1799:\t\t\terr = -EINVAL;\n1800:\t\t\tgoto out;\n1801:\t\t}\n1802:\t\n1803:\t\tif (payload_len \u003e len) {\n1804:\t\t\tpayload_len = len;\n1805:\t\t\tmsg-\u003emsg_flags |= MSG_TRUNC;\n1806:\t\t}\n1807:\t\n1808:\t\t/* Place the datagram payload in the user's iovec. */\n1809:\t\terr = skb_copy_datagram_msg(skb, sizeof(*dg), msg, payload_len);\n1810:\t\tif (err)\n1811:\t\t\tgoto out;\n1812:\t\n1813:\t\tif (msg-\u003emsg_name) {\n1814:\t\t\t/* Provide the address of the sender. */\n1815:\t\t\tDECLARE_SOCKADDR(struct sockaddr_vm *, vm_addr, msg-\u003emsg_name);\n1816:\t\t\tvsock_addr_init(vm_addr, dg-\u003esrc.context, dg-\u003esrc.resource);\n1817:\t\t\tmsg-\u003emsg_namelen = sizeof(*vm_addr);\n1818:\t\t}\n1819:\t\terr = payload_len;\n1820:\t\n1821:\tout:\n1822:\t\tskb_free_datagram(\u0026vsk-\u003esk, skb);\n1823:\t\treturn err;\n1824:\t}\n"
}

83/3 2026/10/29 04:49 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 91623
							output: 21
							thoughts: 279
						

Thoughts:
**Analyzing Datagram Dequeue Implementations**

Okay, let's dive into these datagram dequeue functions. I'm comparing `virtio_transport_dgram_dequeue` with `vmci_transport_dgram_dequeue`. Right off the bat, I see a key difference in how they handle the datagram structure and payload copying. In `vmci_transport_dgram_dequeue`, they cast `skb->data` directly to a `vmci_datagram` struct, then use `skb_copy_datagram_msg`. But over in `virtio_transport_dgram_dequeue`, things look... different. I see `virtio_vsock_hdr(skb)` being used, and then `skb_copy_datagram_iter`.

Hold on a second! This is interesting. In `virtio-vsock`, where exactly *is* that header (`hdr`) located relative to the payload within the `skb`? That's the crucial question. I need to understand this memory layout. The differences in copying functions suggest that the layout might be non-trivial.

Right, I need to check `virtio_vsock_hdr(skb)` to figure out where that header is in relation to the `skb`. So, let's go ahead and track down that function's definition. A quick `grep` for `virtio_vsock_hdr` should do the trick. That should tell me everything I need to know about how the header and payload are packed within the `skb` in the virtio-vsock case. This is key to understanding the dequeue logic and how it's handled.



84/3 2026/10/29 04:49 tool grepper
0m Args:
{
  "Expression": "virtio_vsock_hdr"
}

Results:
{
  "Output": "\nFull output is too long, showing 200 out of 258 lines.\nUse more precise expression or PathPrefixes if possible.\n\nNumber of matching lines per file (6 files in total):\ndrivers/vhost/vsock.c:7\ninclude/linux/virtio_vsock.h:3\ninclude/uapi/linux/virtio_vsock.h:1\ninclude/uapi/linux/vsockmon.h:1\nnet/vmw_vsock/virtio_transport.c:7\nnet/vmw_vsock/virtio_transport_common.c:36\n\ndrivers/vhost/vsock.c=110=vhost_transport_do_send_pkt(struct vhost_vsock *vsock,\n--\ndrivers/vhost/vsock.c-129-\tdo {\ndrivers/vhost/vsock.c:130:\t\tstruct virtio_vsock_hdr *hdr;\ndrivers/vhost/vsock.c-131-\t\tsize_t iov_len, payload_len;\n--\ndrivers/vhost/vsock.c-181-\t\tpayload_len = skb-\u003elen - offset;\ndrivers/vhost/vsock.c:182:\t\thdr = virtio_vsock_hdr(skb);\ndrivers/vhost/vsock.c-183-\n--\ndrivers/vhost/vsock.c=300=vhost_transport_send_pkt(struct sk_buff *skb, struct net *net)\ndrivers/vhost/vsock.c-301-{\ndrivers/vhost/vsock.c:302:\tstruct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);\ndrivers/vhost/vsock.c-303-\tstruct vhost_vsock *vsock;\n--\ndrivers/vhost/vsock.c=370=vhost_vsock_alloc_skb(struct vhost_virtqueue *vq,\n--\ndrivers/vhost/vsock.c-372-{\ndrivers/vhost/vsock.c:373:\tstruct virtio_vsock_hdr *hdr;\ndrivers/vhost/vsock.c-374-\tstruct iov_iter iov_iter;\n--\ndrivers/vhost/vsock.c-397-\ndrivers/vhost/vsock.c:398:\thdr = virtio_vsock_hdr(skb);\ndrivers/vhost/vsock.c-399-\tnbytes = copy_from_iter(hdr, sizeof(*hdr), \u0026iov_iter);\n--\ndrivers/vhost/vsock.c=550=static void vhost_vsock_handle_tx_kick(struct vhost_work *work)\n--\ndrivers/vhost/vsock.c-570-\tdo {\ndrivers/vhost/vsock.c:571:\t\tstruct virtio_vsock_hdr *hdr;\ndrivers/vhost/vsock.c-572-\n--\ndrivers/vhost/vsock.c-604-\ndrivers/vhost/vsock.c:605:\t\thdr = virtio_vsock_hdr(skb);\ndrivers/vhost/vsock.c-606-\n--\ninclude/linux/virtio_vsock.h-9-\ninclude/linux/virtio_vsock.h:10:#define VIRTIO_VSOCK_SKB_HEADROOM (sizeof(struct virtio_vsock_hdr))\ninclude/linux/virtio_vsock.h-11-\ninclude/linux/virtio_vsock.h=12=struct virtio_vsock_skb_cb {\n--\ninclude/linux/virtio_vsock.h-22-\ninclude/linux/virtio_vsock.h:23:static inline struct virtio_vsock_hdr *virtio_vsock_hdr(struct sk_buff *skb)\ninclude/linux/virtio_vsock.h-24-{\ninclude/linux/virtio_vsock.h:25:\treturn (struct virtio_vsock_hdr *)skb-\u003ehead;\ninclude/linux/virtio_vsock.h-26-}\n--\ninclude/uapi/linux/virtio_vsock.h=53=struct virtio_vsock_event {\n--\ninclude/uapi/linux/virtio_vsock.h-56-\ninclude/uapi/linux/virtio_vsock.h:57:struct virtio_vsock_hdr {\ninclude/uapi/linux/virtio_vsock.h-58-\t__le64\tsrc_cid;\n--\ninclude/uapi/linux/vsockmon.h=53=enum af_vsockmon_transport {\n--\ninclude/uapi/linux/vsockmon.h-56-\ninclude/uapi/linux/vsockmon.h:57:\t/* Transport header type: struct virtio_vsock_hdr */\ninclude/uapi/linux/vsockmon.h-58-\tAF_VSOCK_TRANSPORT_VIRTIO = 2,\n--\nnet/vmw_vsock/virtio_transport.c=103=static int virtio_transport_send_skb(struct sk_buff *skb, struct virtqueue *vq,\n--\nnet/vmw_vsock/virtio_transport.c-109-\tsgs = vsock-\u003eout_sgs;\nnet/vmw_vsock/virtio_transport.c:110:\tsg_init_one(sgs[out_sg], virtio_vsock_hdr(skb),\nnet/vmw_vsock/virtio_transport.c:111:\t\t    sizeof(*virtio_vsock_hdr(skb)));\nnet/vmw_vsock/virtio_transport.c-112-\tout_sg++;\n--\nnet/vmw_vsock/virtio_transport.c-126-\t\t */\nnet/vmw_vsock/virtio_transport.c:127:\t\tWARN_ON_ONCE(skb_headroom(skb) != sizeof(*virtio_vsock_hdr(skb)));\nnet/vmw_vsock/virtio_transport.c-128-\n--\nnet/vmw_vsock/virtio_transport.c=239=virtio_transport_send_pkt(struct sk_buff *skb, struct net *net)\nnet/vmw_vsock/virtio_transport.c-240-{\nnet/vmw_vsock/virtio_transport.c:241:\tstruct virtio_vsock_hdr *hdr;\nnet/vmw_vsock/virtio_transport.c-242-\tstruct virtio_vsock *vsock;\n--\nnet/vmw_vsock/virtio_transport.c-244-\nnet/vmw_vsock/virtio_transport.c:245:\thdr = virtio_vsock_hdr(skb);\nnet/vmw_vsock/virtio_transport.c-246-\n--\nnet/vmw_vsock/virtio_transport.c=675=static void virtio_transport_rx_work(struct work_struct *work)\n--\nnet/vmw_vsock/virtio_transport.c-691-\t\t\tunsigned int len, payload_len;\nnet/vmw_vsock/virtio_transport.c:692:\t\t\tstruct virtio_vsock_hdr *hdr;\nnet/vmw_vsock/virtio_transport.c-693-\t\t\tstruct sk_buff *skb;\n--\nnet/vmw_vsock/virtio_transport.c-715-\nnet/vmw_vsock/virtio_transport.c:716:\t\t\thdr = virtio_vsock_hdr(skb);\nnet/vmw_vsock/virtio_transport.c-717-\t\t\tpayload_len = le32_to_cpu(hdr-\u003elen);\n--\nnet/vmw_vsock/virtio_transport_common.c=95=static void virtio_transport_init_hdr(struct sk_buff *skb,\n--\nnet/vmw_vsock/virtio_transport_common.c-102-{\nnet/vmw_vsock/virtio_transport_common.c:103:\tstruct virtio_vsock_hdr *hdr;\nnet/vmw_vsock/virtio_transport_common.c-104-\nnet/vmw_vsock/virtio_transport_common.c:105:\thdr = virtio_vsock_hdr(skb);\nnet/vmw_vsock/virtio_transport_common.c-106-\thdr-\u003etype\t= cpu_to_le16(info-\u003etype);\n--\nnet/vmw_vsock/virtio_transport_common.c=119=static struct sk_buff *virtio_transport_build_skb(void *opaque)\nnet/vmw_vsock/virtio_transport_common.c-120-{\nnet/vmw_vsock/virtio_transport_common.c:121:\tstruct virtio_vsock_hdr *pkt_hdr;\nnet/vmw_vsock/virtio_transport_common.c-122-\tstruct sk_buff *pkt = opaque;\n--\nnet/vmw_vsock/virtio_transport_common.c-130-\t */\nnet/vmw_vsock/virtio_transport_common.c:131:\tpkt_hdr = virtio_vsock_hdr(pkt);\nnet/vmw_vsock/virtio_transport_common.c-132-\tpayload_len = le32_to_cpu(pkt_hdr-\u003elen);\n--\nnet/vmw_vsock/virtio_transport_common.c=220=static struct sk_buff *virtio_transport_alloc_skb(struct virtio_vsock_pkt_info *info,\n--\nnet/vmw_vsock/virtio_transport_common.c-275-\t\t    info-\u003etype == VIRTIO_VSOCK_TYPE_SEQPACKET) {\nnet/vmw_vsock/virtio_transport_common.c:276:\t\t\tstruct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);\nnet/vmw_vsock/virtio_transport_common.c-277-\n--\nnet/vmw_vsock/virtio_transport_common.c=476=static bool virtio_transport_can_collapse(struct sk_buff *skb)\n--\nnet/vmw_vsock/virtio_transport_common.c-484-\t\treturn false;\nnet/vmw_vsock/virtio_transport_common.c:485:\tif (le32_to_cpu(virtio_vsock_hdr(skb)-\u003eflags) \u0026 VIRTIO_VSOCK_SEQ_EOM)\nnet/vmw_vsock/virtio_transport_common.c-486-\t\treturn false;\n--\nnet/vmw_vsock/virtio_transport_common.c=518=virtio_transport_collapse_rx_queue(struct virtio_vsock_sock *vvs,\n--\nnet/vmw_vsock/virtio_transport_common.c-527-\tskb_queue_walk_safe(\u0026vvs-\u003erx_queue, skb, next_skb) {\nnet/vmw_vsock/virtio_transport_common.c:528:\t\tstruct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);\nnet/vmw_vsock/virtio_transport_common.c-529-\t\tu32 src_off = VIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset;\n--\nnet/vmw_vsock/virtio_transport_common.c-579-\nnet/vmw_vsock/virtio_transport_common.c:580:\t\t\tmemcpy(virtio_vsock_hdr(new_skb), hdr,\nnet/vmw_vsock/virtio_transport_common.c:581:\t\t\t       sizeof(struct virtio_vsock_hdr));\nnet/vmw_vsock/virtio_transport_common.c:582:\t\t\tvirtio_vsock_hdr(new_skb)-\u003elen = 0;\nnet/vmw_vsock/virtio_transport_common.c-583-\t\t}\n--\nnet/vmw_vsock/virtio_transport_common.c-595-\nnet/vmw_vsock/virtio_transport_common.c:596:\t\tle32_add_cpu(\u0026virtio_vsock_hdr(new_skb)-\u003elen, src_len);\nnet/vmw_vsock/virtio_transport_common.c:597:\t\tvirtio_vsock_hdr(new_skb)-\u003eflags |= hdr-\u003eflags;\nnet/vmw_vsock/virtio_transport_common.c-598-\n--\nnet/vmw_vsock/virtio_transport_common.c=643=void virtio_transport_inc_tx_pkt(struct virtio_vsock_sock *vvs, struct sk_buff *skb)\nnet/vmw_vsock/virtio_transport_common.c-644-{\nnet/vmw_vsock/virtio_transport_common.c:645:\tstruct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);\nnet/vmw_vsock/virtio_transport_common.c-646-\n--\nnet/vmw_vsock/virtio_transport_common.c=767=virtio_transport_stream_do_dequeue(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-812-\t\tif (skb-\u003elen == VIRTIO_VSOCK_SKB_CB(skb)-\u003eoffset) {\nnet/vmw_vsock/virtio_transport_common.c:813:\t\t\tdequeued = le32_to_cpu(virtio_vsock_hdr(skb)-\u003elen);\nnet/vmw_vsock/virtio_transport_common.c-814-\t\t\t__skb_unlink(skb, \u0026vvs-\u003erx_queue);\n--\nnet/vmw_vsock/virtio_transport_common.c=850=virtio_transport_seqpacket_do_peek(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-867-\tskb_queue_walk(\u0026vvs-\u003erx_queue, skb) {\nnet/vmw_vsock/virtio_transport_common.c:868:\t\tstruct virtio_vsock_hdr *hdr;\nnet/vmw_vsock/virtio_transport_common.c-869-\n--\nnet/vmw_vsock/virtio_transport_common.c-891-\t\ttotal += skb-\u003elen;\nnet/vmw_vsock/virtio_transport_common.c:892:\t\thdr = virtio_vsock_hdr(skb);\nnet/vmw_vsock/virtio_transport_common.c-893-\n--\nnet/vmw_vsock/virtio_transport_common.c=907=static int virtio_transport_seqpacket_do_dequeue(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-924-\twhile (!msg_ready) {\nnet/vmw_vsock/virtio_transport_common.c:925:\t\tstruct virtio_vsock_hdr *hdr;\nnet/vmw_vsock/virtio_transport_common.c-926-\t\tsize_t pkt_len;\n--\nnet/vmw_vsock/virtio_transport_common.c-930-\t\t\tbreak;\nnet/vmw_vsock/virtio_transport_common.c:931:\t\thdr = virtio_vsock_hdr(skb);\nnet/vmw_vsock/virtio_transport_common.c-932-\t\tpkt_len = (size_t)le32_to_cpu(hdr-\u003elen);\n--\nnet/vmw_vsock/virtio_transport_common.c=1039=virtio_transport_dgram_dequeue(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-1043-\tstruct sock *sk = sk_vsock(vsk);\nnet/vmw_vsock/virtio_transport_common.c:1044:\tstruct virtio_vsock_hdr *hdr;\nnet/vmw_vsock/virtio_transport_common.c-1045-\tstruct sk_buff *skb;\n--\nnet/vmw_vsock/virtio_transport_common.c-1055-\nnet/vmw_vsock/virtio_transport_common.c:1056:\thdr = virtio_vsock_hdr(skb);\nnet/vmw_vsock/virtio_transport_common.c-1057-\tpayload_len = skb-\u003elen;\n--\nnet/vmw_vsock/virtio_transport_common.c=1396=static int virtio_transport_reset(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-1406-\t/* Send RST only if the original pkt is not a RST pkt */\nnet/vmw_vsock/virtio_transport_common.c:1407:\tif (skb \u0026\u0026 le16_to_cpu(virtio_vsock_hdr(skb)-\u003eop) == VIRTIO_VSOCK_OP_RST)\nnet/vmw_vsock/virtio_transport_common.c-1408-\t\treturn 0;\n--\nnet/vmw_vsock/virtio_transport_common.c=1420=static int virtio_transport_reset_no_sock(const struct virtio_transport *t,\n--\nnet/vmw_vsock/virtio_transport_common.c-1422-{\nnet/vmw_vsock/virtio_transport_common.c:1423:\tstruct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);\nnet/vmw_vsock/virtio_transport_common.c-1424-\tstruct virtio_vsock_pkt_info info = {\n--\nnet/vmw_vsock/virtio_transport_common.c=1573=virtio_transport_recv_connecting(struct sock *sk,\n--\nnet/vmw_vsock/virtio_transport_common.c-1575-{\nnet/vmw_vsock/virtio_transport_common.c:1576:\tstruct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);\nnet/vmw_vsock/virtio_transport_common.c-1577-\tstruct vsock_sock *vsk = vsock_sk(sk);\n--\nnet/vmw_vsock/virtio_transport_common.c=1610=virtio_transport_recv_enqueue(struct vsock_sock *vsk,\n--\nnet/vmw_vsock/virtio_transport_common.c-1615-\tu32 len, queue_max, queue_len;\nnet/vmw_vsock/virtio_transport_common.c:1616:\tstruct virtio_vsock_hdr *hdr;\nnet/vmw_vsock/virtio_transport_common.c-1617-\nnet/vmw_vsock/virtio_transport_common.c:1618:\thdr = virtio_vsock_hdr(skb);\nnet/vmw_vsock/virtio_transport_common.c-1619-\tlen = le32_to_cpu(hdr-\u003elen);\n\n"
}

85/3 2026/10/29 04:49 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 97237
							output: 40
							thoughts: 1233
						

Thoughts:
**Analyzing virtio_vsock Datagram Handling for Potential Vulnerabilities**

Okay, let's break this down. First, I'm observing the structure here. `#define VIRTIO_VSOCK_SKB_HEADROOM` defines the size of `virtio_vsock_hdr`, and `virtio_vsock_hdr(skb)` casts `skb->head` to that struct. The crucial point is that `skb->data` *starts after* that headroom, thanks to `skb_reserve`. Therefore, `skb->data` contains *only* the payload, and `skb->len` is, naturally, the payload's length. The header itself is tucked away in `skb->head`.

Now, in `virtio_transport_dgram_dequeue`, this is pretty clear: `hdr` points to the header, `payload_len` gets the correct payload length, and `skb_copy_datagram_iter` copies `payload_len` bytes from the starting offset of the `skb` (which, again, is `skb->data`), into `msg->msg_iter`. So the data is the payload. Good.

Next, I need to check for any uninitialized data reads or leaks. I’m thinking about `virtio_transport_dgram_dequeue` again. I see that if `msg->msg_name` is set, the code initializes `vm_addr` with `vsock_addr_init`, which calls `memset`, which completely zeroes out `vm_addr`. The `msg_namelen` is then set to the size of `vm_addr`. This means all fields, including padding, are initialized, so there's no way to leak uninitialized data through `msg_name`. That's a relief.

Now, let's look at `VIRTIO_VSOCK_SKB_CB(skb)->to_host`. Could this be read uninitialized? I need to trace where `skb` is created and queued to `sk->sk_receive_queue`. Okay, following the execution, starting with `virtio_transport_recv_pkt`, I see this conditional branch and then the important line: `VIRTIO_VSOCK_SKB_CB(skb)->to_host = t->transport.get_local_cid() > VMADDR_CID_HOST;`. Crucially, this assignment happens *right before* `sock_queue_rcv_skb`, meaning `to_host` is always initialized before `virtio_transport_dgram_dequeue` ever sees the `skb`. So there's no risk there.

Finally, what about the loopback scenario? I notice `vsock_loopback.c` uses the same `virtio_transport_dgram_dequeue` function. When loopback sends a packet, `virtio_transport_send_pkt_info` calls `loopback_transport.send_pkt`. So the same logic should apply there as well.

This is all pretty clean and well-defined, and I don't see any immediate vulnerabilities in terms of uninitialized data usage in this code path.



86/3 2026/10/29 04:49 tool grepper
0m Args:
{
  "Expression": "loopback_transport",
  "PathPrefixes": [
    "net/vmw_vsock/vsock_loopback.c"
  ]
}

Results:
{
  "Output": "net/vmw_vsock/vsock_loopback.c=58=static bool vsock_loopback_msgzerocopy_allow(void)\n--\nnet/vmw_vsock/vsock_loopback.c-62-\nnet/vmw_vsock/vsock_loopback.c:63:static struct virtio_transport loopback_transport = {\nnet/vmw_vsock/vsock_loopback.c-64-\t.transport = {\n--\nnet/vmw_vsock/vsock_loopback.c=124=static void vsock_loopback_work(struct work_struct *work)\n--\nnet/vmw_vsock/vsock_loopback.c-142-\t\tvirtio_transport_deliver_tap_pkt(skb);\nnet/vmw_vsock/vsock_loopback.c:143:\t\tvirtio_transport_recv_pkt(\u0026loopback_transport, skb,\nnet/vmw_vsock/vsock_loopback.c-144-\t\t\t\t\t  sock_net(skb-\u003esk));\n--\nnet/vmw_vsock/vsock_loopback.c=148=static int __init vsock_loopback_init(void)\n--\nnet/vmw_vsock/vsock_loopback.c-159-\nnet/vmw_vsock/vsock_loopback.c:160:\tret = vsock_core_register(\u0026loopback_transport.transport,\nnet/vmw_vsock/vsock_loopback.c-161-\t\t\t\t  VSOCK_TRANSPORT_F_LOCAL);\n--\nnet/vmw_vsock/vsock_loopback.c=172=static void __exit vsock_loopback_exit(void)\n--\nnet/vmw_vsock/vsock_loopback.c-175-\nnet/vmw_vsock/vsock_loopback.c:176:\tvsock_core_unregister(\u0026loopback_transport.transport);\nnet/vmw_vsock/vsock_loopback.c-177-\n"
}

87/3 2026/10/29 04:49 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 99714
							output: 25
							thoughts: 799
						

Thoughts:
**Analyzing the `tx_accounted` Flag in Virtio-VSOCK Datagram Handling**

Okay, let's break this down. My initial observation is that `vsock_loopback.c` calls `virtio_transport_recv_pkt`, which, interestingly, sets `VIRTIO_VSOCK_SKB_CB(skb)->to_host` for loopback packets, just like regular vsock. That's a point of potential connection.

Now, the `tx_accounted` flag... I need to understand its lifecycle. Looking at the code, I see it defined in `virtio_vsock_skb_cb` (good, that's expected), and it gets set to `true` in `virtio_transport_send_pkt_info` *only* for datagrams (`VIRTIO_VSOCK_TYPE_DGRAM`). This also increments `vvs->bytes_unsent`, which makes sense for accounting. The destructor gets set here too.

The critical bit is `virtio_transport_consume_skb_sent`. This function decrements `bytes_unsent` *if* the socket is a datagram socket and `tx_accounted` is true. Then, *regardless* of the accounting, `tx_accounted` is set to `false` for datagram sockets.

Here's the problem that jumps out at me: `virtio_transport_consume_skb_sent` can be called from `virtio_transport_dgram_skb_destructor`, and that destructor is set *only* if `tx_accounted` is set in the first place, but is this also true for loopback packets? I need to know where else `virtio_transport_consume_skb_sent` is called! Is there a path where the destructor can be called on an SKB where tx_accounted wasn't initialized? If so, that's a serious bug. Time to trace the callers and understand the flows.



88/3 2026/10/29 04:50 tool grepper
0m Args:
{
  "Expression": "virtio_transport_consume_skb_sent"
}

Results:
{
  "Output": "drivers/vhost/vsock.c=110=vhost_transport_do_send_pkt(struct vhost_vsock *vsock,\n--\ndrivers/vhost/vsock.c-274-\ndrivers/vhost/vsock.c:275:\t\t\tvirtio_transport_consume_skb_sent(skb, true);\ndrivers/vhost/vsock.c-276-\t\t}\n--\ninclude/linux/virtio_vsock.h=227=ssize_t virtio_transport_unsent_bytes(struct vsock_sock *vsk);\ninclude/linux/virtio_vsock.h-228-\ninclude/linux/virtio_vsock.h:229:void virtio_transport_consume_skb_sent(struct sk_buff *skb,\ninclude/linux/virtio_vsock.h-230-\t\t\t\t       bool consume);\n--\nnet/vmw_vsock/virtio_transport.c=353=static void virtio_transport_tx_work(struct work_struct *work)\n--\nnet/vmw_vsock/virtio_transport.c-372-\t\twhile ((skb = virtqueue_get_buf(vq, \u0026len)) != NULL) {\nnet/vmw_vsock/virtio_transport.c:373:\t\t\tvirtio_transport_consume_skb_sent(skb, true);\nnet/vmw_vsock/virtio_transport.c-374-\t\t\tadded = true;\n--\nnet/vmw_vsock/virtio_transport_common.c=213=static void virtio_transport_dgram_skb_destructor(struct sk_buff *skb)\nnet/vmw_vsock/virtio_transport_common.c-214-{\nnet/vmw_vsock/virtio_transport_common.c:215:\tvirtio_transport_consume_skb_sent(skb, false);\nnet/vmw_vsock/virtio_transport_common.c-216-\tsock_wfree(skb);\n--\nnet/vmw_vsock/virtio_transport_common.c=653=EXPORT_SYMBOL_GPL(virtio_transport_inc_tx_pkt);\nnet/vmw_vsock/virtio_transport_common.c-654-\nnet/vmw_vsock/virtio_transport_common.c:655:void virtio_transport_consume_skb_sent(struct sk_buff *skb, bool consume)\nnet/vmw_vsock/virtio_transport_common.c-656-{\n--\nnet/vmw_vsock/virtio_transport_common.c-677-}\nnet/vmw_vsock/virtio_transport_common.c:678:EXPORT_SYMBOL_GPL(virtio_transport_consume_skb_sent);\nnet/vmw_vsock/virtio_transport_common.c-679-\n--\nnet/vmw_vsock/vsock_loopback.c=124=static void vsock_loopback_work(struct work_struct *work)\n--\nnet/vmw_vsock/vsock_loopback.c-140-\t\t */\nnet/vmw_vsock/vsock_loopback.c:141:\t\tvirtio_transport_consume_skb_sent(skb, false);\nnet/vmw_vsock/vsock_loopback.c-142-\t\tvirtio_transport_deliver_tap_pkt(skb);\n"
}

89/3 2026/10/29 04:50 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 101851
							output: 42
							thoughts: 282
						

Thoughts:
**Analyzing `tx_accounted` in the Virtio vsock Subsystem**

Okay, so I'm digging into the Virtio vsock code again. I'm focusing on how `tx_accounted` is handled in the data path. I recall from tracing `virtio_transport_alloc_skb` that `skb->cb` gets zeroed during SKB allocation, specifically using `skbuff_clear(skb)`. This is the starting point.

Now, for `SOCK_DGRAM` sockets, `virtio_transport_send_pkt_info` explicitly sets `tx_accounted` to `true`. This tells me that this flag is critical in tracking the lifecycle of outgoing packets. The implication is that a datagram has been accounted for transmission.

Then, when the packet is consumed – either through a transmit completion event or during the destructor call, it's checked and then explicitly reset to `false`. So `tx_accounted` seems to function as a clear state marker. I should be able to rely on it. It should always be either zeroed or explicitly assigned. This gives me a good baseline on the lifecycle of the data in flight.

I want to check how the vsock binding process impacts the state of `tx_accounted` on the datagram skb, so I want to consider `vsock_bind_common`. The function itself seems straightforward; it's just a lock-protected call to `__vsock_bind_common`. That's not too interesting at first glance. However, I need to check the lines 945 to 1005 in `net/vmw_vsock/af_vsock.c` to understand the state flow inside the binding. I need to see what happens to the skb there.



90/3 2026/10/29 04:50 tool read-file
0m Args:
{
  "File": "net/vmw_vsock/af_vsock.c",
  "FirstLine": 945,
  "LineCount": 60
}

Results:
{
  "Contents": " 945:\t/**** SOCKET OPERATIONS ****/\n 946:\t\n 947:\tstatic int __vsock_bind_common(struct vsock_sock *vsk,\n 948:\t\t\t\t       struct sockaddr_vm *addr)\n 949:\t{\n 950:\t\tstruct net *net = sock_net(sk_vsock(vsk));\n 951:\t\tstruct sockaddr_vm new_addr;\n 952:\t\n 953:\t\tif (!net-\u003evsock.port)\n 954:\t\t\tnet-\u003evsock.port = get_random_u32_above(LAST_RESERVED_PORT);\n 955:\t\n 956:\t\tvsock_addr_init(\u0026new_addr, addr-\u003esvm_cid, addr-\u003esvm_port);\n 957:\t\n 958:\t\tif (addr-\u003esvm_port == VMADDR_PORT_ANY) {\n 959:\t\t\tbool found = false;\n 960:\t\t\tunsigned int i;\n 961:\t\n 962:\t\t\tfor (i = 0; i \u003c MAX_PORT_RETRIES; i++) {\n 963:\t\t\t\tif (net-\u003evsock.port == VMADDR_PORT_ANY ||\n 964:\t\t\t\t    net-\u003evsock.port \u003c= LAST_RESERVED_PORT)\n 965:\t\t\t\t\tnet-\u003evsock.port = LAST_RESERVED_PORT + 1;\n 966:\t\n 967:\t\t\t\tnew_addr.svm_port = net-\u003evsock.port++;\n 968:\t\n 969:\t\t\t\tif (!__vsock_find_bound_socket_net(\u0026new_addr, net)) {\n 970:\t\t\t\t\tfound = true;\n 971:\t\t\t\t\tbreak;\n 972:\t\t\t\t}\n 973:\t\t\t}\n 974:\t\n 975:\t\t\tif (!found)\n 976:\t\t\t\treturn -EADDRNOTAVAIL;\n 977:\t\t} else {\n 978:\t\t\t/* If port is in reserved range, ensure caller\n 979:\t\t\t * has necessary privileges.\n 980:\t\t\t */\n 981:\t\t\tif (addr-\u003esvm_port \u003c= LAST_RESERVED_PORT \u0026\u0026\n 982:\t\t\t    !capable(CAP_NET_BIND_SERVICE)) {\n 983:\t\t\t\treturn -EACCES;\n 984:\t\t\t}\n 985:\t\n 986:\t\t\tif (__vsock_find_bound_socket_net(\u0026new_addr, net))\n 987:\t\t\t\treturn -EADDRINUSE;\n 988:\t\t}\n 989:\t\n 990:\t\tvsock_addr_init(\u0026vsk-\u003elocal_addr, new_addr.svm_cid, new_addr.svm_port);\n 991:\t\n 992:\t\t/* Remove connection oriented sockets from the unbound list and add them\n 993:\t\t * to the hash table for easy lookup by its address.  The unbound list\n 994:\t\t * is simply an extra entry at the end of the hash table, a trick used\n 995:\t\t * by AF_UNIX.\n 996:\t\t */\n 997:\t\t__vsock_remove_bound(vsk);\n 998:\t\t__vsock_insert_bound(vsock_bound_sockets(\u0026vsk-\u003elocal_addr), vsk);\n 999:\t\n1000:\t\treturn 0;\n1001:\t}\n1002:\t\n1003:\t/**\n1004:\t * vsock_bind_common - bind a socket using the core port allocator\n"
}

91/3 2026/10/29 04:50 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 103479
							output: 505
							thoughts: 3066
						

Thoughts:
**KMSAN Assessment: virtio-vsock SOCK_DGRAM Implementation**

Okay, let's break this down. We're looking at the introduction of datagram support for virtio-vsock and vhost-vsock. My initial thought process is to meticulously trace potential uninitialized memory exposures, especially those that KMSAN is uniquely positioned to catch.

First, I examined `vsock_bind_common` and the initialization of `new_addr` and `vsk->local_addr` with `vsock_addr_init`. Since `vsock_addr_init` utilizes `memset` to zero out the structure *before* filling in the relevant fields, any padding or unused parts are guaranteed to be initialized. That's a good starting point.

Next, I delved into `vsock_dgram_sendmsg`.  The crucial point here is how the `remote_addr` is handled when `msg->msg_name` is provided.  We copy to `remote_addr = other`, where `other` is within the user's provided buffer.  However, this doesn't automatically mean an uninitialized read, because `vsock_addr_validate` is called. Moreover, the address is initialized by `vsock_addr_init` so we are safe.

I considered potential issues with `VMADDR_CID_ANY` and `transport->get_local_cid()`, along with `to_host` and how the transport is selected, which could also lead to initialization problems. But the validation checks and address initialization with `vsock_addr_init` seem to address these issues.

I then looked at `vsock_poll`. This is a standard socket polling implementation, with no direct exposure to uninitialized memory. The `mask` variable is initialized before use.

Moving on to `hyperv_transport.c`, it's just rejecting DGRAM, so it's not a source of the issue.  `vmci_transport_dgram_payload_len` returns a `size_t` from `skb->len`. This is used in an ioctl, and the check there.

I specifically checked `vsock_bpf.c`. The code rejects BPF for DGRAM, so we don't have BPF accessing any memory.

Now, the core assessment: Does this patch series *introduce* any KMSAN-specific risks?

I went through the four KMSAN "NeedsKMSAN=true" criteria, systematically:

1.  **Kernel structures to user space**:  `put_user` and `skb_copy_datagram_iter` are used.  The `put_user` case involves initialized local variables. The  `skb_copy_datagram_iter` case looks at `vsock_addr_init` and sees that the address is initialized and validated (zeroed). The payload comes directly from the network, after it is trimmed, so it's safe.
2.  **Conditional logic and uninitialized variables**: All new variables are explicitly initialized (e.g., `dgram_allow`, `to_host`, etc.). There is no dependency on uninitialized variables. `VIRTIO_VSOCK_SKB_CB(skb)->to_host` and `VIRTIO_VSOCK_SKB_CB(skb)->tx_accounted` are set and cleared before being used, inside `virtio_transport_recv_pkt` and `virtio_transport_send_pkt_info` respectively.
3.  **Complex data structures**: `dgram_allow` is set in `dev_open` and `probe`, and is initialized before use.  The `in_bufs` scatterlist in `virtio_vsock` is reinitialized on every fill.
4.  **Bounds checks and lengths**: The code diligently checks the lengths of packets, ensuring that the payload length is less than or equal to the total length received from the virtio device, and within the bounds of a single descriptor chain for datagrams.  `pskb_trim_unique` is used.  `skb_copy_datagram_iter` is also safe because it is capped by what the device has.

What other bugs *could* be present? Concurrency issues, refcounting errors, out-of-bounds accesses. I will use standard kernel tools to detect these: KASAN, LOCKDEP.

My conclusion is that this patch series does *not* introduce KMSAN-specific uninitialized memory risks. All structures are properly initialized before being accessed, and all bounds checks are present to prevent out-of-bounds reads. Therefore, `NeedsKMSAN = false`.



92/3 2026/10/29 04:51 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch series implements SOCK_DGRAM support for virtio-vsock and vhost-vsock, adding datagram enqueue/dequeue, transport selection, loopback and peer routing, and ioctl/poll handling.\n\nKMSAN vs. KASAN/standard detectors evaluation:\n1. Potential bugs introduced or touched by this patch series primarily concern socket buffer lifecycle management (skb allocation, trimming, freeing via kfree_skb/sock_wfree), module and socket reference counting (try_module_get/module_put, sock_put), locking synchronization (vsock_register_mutex, vsock_table_lock, vvs-\u003etx_lock, socket locks), and bounds/truncation checks. These risks are covered by standard bug detectors, KASAN, and LOCKDEP.\n2. Information leaks / copy-to-user inspection:\n   - In `virtio_transport_dgram_dequeue()`, user addresses are constructed using `vsock_addr_init()`, which zeroes the entire `struct sockaddr_vm` (including structure padding) via `memset` prior to initializing field values.\n   - Datagram payloads copied to user space via `skb_copy_datagram_iter()` are bounded by `payload_len`, which is strictly validated against the received descriptor length `len` and trimmed with `pskb_trim_unique()`.\n   - The ioctl handler for SIOCINQ reports `n_bytes`, a cleanly initialized scalar integer.\n3. Uninitialized state inspection:\n   - `struct virtio_vsock_skb_cb` fields (`to_host`, `tx_accounted`) are stored in `skb-\u003ecb`, which is zeroed on skb allocation (`skbuff_clear()`), and are explicitly assigned before being queried in control flow or copied into socket structures.\n   - In `virtio_vsock_rx_fill()`, the scatterlist array `vsock-\u003ein_bufs` is initialized with `sg_init_table()` prior to descriptor submission.\n\nBecause all newly introduced structures, variables, and buffer copies are properly zeroed or initialized, and any memory-safety risks are detectable by KASAN and LOCKDEP, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch series implements SOCK_DGRAM support for virtio-vsock and vhost-vsock, adding datagram enqueue/dequeue, transport selection, loopback and peer routing, and ioctl/poll handling.\n\nKMSAN vs. KASAN/standard detectors evaluation:\n1. Potential bugs introduced or touched by this patch series primarily concern socket buffer lifecycle management (skb allocation, trimming, freeing via kfree_skb/sock_wfree), module and socket reference counting (try_module_get/module_put, sock_put), locking synchronization (vsock_register_mutex, vsock_table_lock, vvs-\u003etx_lock, socket locks), and bounds/truncation checks. These risks are covered by standard bug detectors, KASAN, and LOCKDEP.\n2. Information leaks / copy-to-user inspection:\n   - In `virtio_transport_dgram_dequeue()`, user addresses are constructed using `vsock_addr_init()`, which zeroes the entire `struct sockaddr_vm` (including structure padding) via `memset` prior to initializing field values.\n   - Datagram payloads copied to user space via `skb_copy_datagram_iter()` are bounded by `payload_len`, which is strictly validated against the received descriptor length `len` and trimmed with `pskb_trim_unique()`.\n   - The ioctl handler for SIOCINQ reports `n_bytes`, a cleanly initialized scalar integer.\n3. Uninitialized state inspection:\n   - `struct virtio_vsock_skb_cb` fields (`to_host`, `tx_accounted`) are stored in `skb-\u003ecb`, which is zeroed on skb allocation (`skbuff_clear()`), and are explicitly assigned before being queried in control flow or copied into socket structures.\n   - In `virtio_vsock_rx_fill()`, the scatterlist array `vsock-\u003ein_bufs` is initialized with `sg_init_table()` prior to descriptor submission.\n\nBecause all newly introduced structures, variables, and buffer copies are properly zeroed or initialized, and any memory-safety risks are detectable by KASAN and LOCKDEP, a dedicated KMSAN fuzzing 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)