Skip to content

executor: add syz_vhost_vsock_setup and syz_vhost_vsock_send - #8049

Open
roglew wants to merge 2 commits into
google:masterfrom
roglew:improve-vsock-fuzzing
Open

roglew wants to merge 2 commits into
google:masterfrom
roglew:improve-vsock-fuzzing

Conversation

@roglew

@roglew roglew commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #8086 (first commit); the second commit is this PR.

The vhost-vsock descriptions only reach the ioctl surface of /dev/vhost-vsock. Nothing acts as the guest, so vhost_vsock_handle_tx_kick(), virtio_transport_recv_pkt() and the reply path in vhost_transport_do_send_pkt() only run on ring state the fuzzer builds by chance, which is almost never a valid packet.

This adds two pseudo-syscalls that make the fuzzed kernel the host and the executor the guest, as suggested in the review.

  • syz_vhost_vsock_setup(cid) opens /dev/vhost-vsock and identity-maps the rx and tx virtqueues plus one 4 KB buffer per descriptor with VHOST_SET_MEM_TABLE. It negotiates VIRTIO_F_VERSION_1 (and VIRTIO_VSOCK_F_SEQPACKET where available), assigns the guest CID, posts all rx buffers and starts the device. It returns the vhost fd as a vhost_vsock_guest resource, a vhost_vsock, so the existing ioctl$VHOST_* calls can race a running device.
  • syz_vhost_vsock_send(fd, pkt, len) puts one packet on the tx virtqueue and kicks the host, after handing back the rx buffers the host has used. The executor fills in src_cid and dst_cid; the rest of virtio_vsock_hdr and the payload come from the program.

Coverage was measured with KCOV on Linux 6.18 for the single seed sys/linux/test/vsock_vhost_guest, against the existing ioctl-only vhost seed. drivers/vhost/vsock.c goes from 26 to 49 of 147 PCs, net/vmw_vsock/virtio_transport_common.c from 34 to 95 of 284, and net/vmw_vsock/af_vsock.c from 52 to 110 of 421. The comment below has the function list and a 13-minute manager run that hit WARNING in check_mm.

Tested: make descriptions, make executor with -Werror, pkg/runtest TestParsing, pkg/csource tests. The generated C reproducer for the seed compiles with gcc (gnu17, gnu23, -m32) and clang. The seed passes end to end on a 6.18 VM: accept4 returns, recvfrom gets the payload, and the host-initiated connect completes.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for fuzzing the Linux AF_VSOCK socket family, including the addition of a new pseudo-syscall syz_vsock_virtio_reprobe to trigger concurrent driver unbind/bind operations. It updates the system call descriptions for VSOCK sockets (adding SOCK_SEQPACKET, zero-copy, socket diagnostics, and timeout options), implements the reprobe logic in the executor, and adds support for launching QEMU instances with a vhost-vsock-pci device using a randomized guest CID. I have no feedback to provide as there are no review comments to evaluate and the implementation aligns with the repository guidelines.

@roglew
roglew marked this pull request as ready for review October 22, 2026 01:20
@github-actions github-actions Bot added the well-shaped PR has no conflicts and all checks pass label Oct 22, 2026
@github-actions
github-actions Bot requested a review from tarasmadan October 22, 2026 03:17
@a-nogikh
a-nogikh requested review from a-nogikh and removed request for tarasmadan October 22, 2026 08:56
@github-actions github-actions Bot removed the well-shaped PR has no conflicts and all checks pass label Oct 22, 2026
@codecov

codecov Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 52.7%. Comparing base (4ba27f8) to head (b44e7d9).
⚠️ Report is 10 commits behind head on master.

Files with missing lines Patch % Lines
vm/qemu/qemu.go 75.0% 2 Missing ⚠️
Additional details and impacted files
Files with missing lines Coverage Δ
pkg/vminfo/linux_syscalls.go 64.7% <100.0%> (+0.4%) ⬆️
vm/qemu/qemu.go 10.8% <75.0%> (+10.8%) ⬆️

... and 12 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions Bot added the well-shaped PR has no conflicts and all checks pass label Oct 22, 2026

@a-nogikh a-nogikh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!
The first commit looks good to me (left just one comment on it) - could you please look into that and post it as a separate PR that we'll merge quickly?

Regarding the other commits, I haven't looked too closely at the code since I have a concern about the overall approach.

As I understand, here you make the fuzzed VM play the guest side - it can send the packets to the host, but on our usual fuzzing setup nobody will ever answer. I have a suspicion that it will not add much coverage to what we already could reach.

What if we make the fuzzed kernel play the role of the host? E.g. by adding two pseudo syscalls:

  • One that would open and properly configure /dev/vhost-vsock (syz_vhost_vsock_setup), e.g. by using VHOST_SET_MEM_TABLE to map guest's buffer on the host.
  • One that would be the guest and send messages on the mapped ring (syz_vhost_vsock_send).

I'd ask an agent to try it out and see if it improves the coverage as compared to what we already have on syzbot.

Comment thread vm/qemu/qemu_test.go Outdated
}
}

func TestRandomVsockCID(t *testing.T) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this test - it basically tests the Go library's random number generation.

Comment thread vm/qemu/qemu.go Outdated
}
if inst.cfg.Vsock {
args = append(args,
"-device", fmt.Sprintf("vhost-vsock-pci,guest-cid=%v", inst.vsockCID),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that it won't work for all architectures, e.g. on s390x it should have been vhost-vsock-ccw and on arm - vhost-vsock-device (?)

Comment thread sys/linux/socket_vnet.txt Outdated

# MSG_ZEROCOPY is honored once SO_ZEROCOPY is set; completions are read with recvmsg(MSG_ERRQUEUE).
setsockopt$SO_ZEROCOPY_vsock(fd sock_vsock_stream, level const[SOL_SOCKET], opt const[SO_ZEROCOPY], val ptr[in, bool32], len len[val])
sendmsg$vsock_stream(fd sock_vsock_stream, msg ptr[in, send_msghdr], f flags[vsock_send_flags])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The kernel will just reject the messages that have a destination set. Maybe we should add a specialized send_msghdr for the vsock purposes, which would always keep it 0?

https://elixir.bootlin.com/linux/v7.3/source/net/vmw_vsock/af_vsock.c#L2213

`accept4$vsock_stream` passed `addrlen` by value, so `accept4()` always failed
with `-EFAULT` and accepted sockets never reached the fuzzer. The descriptions
also had no `SOCK_SEQPACKET` sockets, no `svm_flags`/`VMADDR_FLAG_TO_HOST`, no
`getsockopt` or 64-bit connect timeout, and no typed `MSG_ZEROCOPY` path. vsock
diag dumps used the 2-byte generic `sock_diag_req`, which
`vsock_diag_handler_dump()` rejects because it needs a 24-byte
`struct vsock_diag_req`.

Fix `accept4$vsock_stream`, split `svm_zero` into `svm_flags` and padding, and
add `socket$vsock_seqpacket`, `getsockopt$vsock`,
`setsockopt$SO_VM_SOCKETS_CONNECT_TIMEOUT_NEW`, `setsockopt$SO_ZEROCOPY_vsock`,
`sendmsg$vsock_stream`, and `sendmsg$vsock_diag`. `sendmsg$vsock_stream` uses a
vsock-specific `send_msghdr_vsock` without a destination, since
`vsock_connectible_sendmsg()` rejects messages that carry one. Add manual seed
programs for loopback stream (including zerocopy and diag), seqpacket, and
vhost-vsock transport selection.
The vhost-vsock descriptions only reach the ioctl surface of
`/dev/vhost-vsock`. Nothing acts as the guest, so
`vhost_vsock_handle_tx_kick()`, `virtio_transport_recv_pkt()` and the reply
path in `vhost_transport_do_send_pkt()` only run on ring state the fuzzer
builds by chance, which is almost never a valid packet.

Add two pseudo-syscalls that make the fuzzed kernel the host and the executor
the guest. `syz_vhost_vsock_setup(cid)` opens `/dev/vhost-vsock`,
identity-maps the rx/tx virtqueues and one buffer per descriptor with
`VHOST_SET_MEM_TABLE`, assigns the guest CID and starts the device.
`syz_vhost_vsock_send(fd, pkt, len)` puts one `virtio_vsock_hdr` + payload
packet on the tx virtqueue and kicks the host. It also hands used rx buffers
back, so host replies keep flowing.

The executor fills in the two CIDs of the header, so every generated packet
passes the host's address check. The rest of the header is a typed syzlang
struct. The guest CID comes from the per-proc range of `vmaddr_cid` `my`, so
host AF_VSOCK sockets can also connect to the emulated guest.

Add a manual test program. A guest REQUEST connects to a host listener, data
flows both ways and the guest shuts down. A host socket bound to a known port
then connects to the guest, which completes the connection with RESPONSE.
@roglew
roglew force-pushed the improve-vsock-fuzzing branch from b44e7d9 to 71dd13a Compare October 30, 2026 19:19
@roglew roglew changed the title sys/linux, vm/qemu, executor: improve AF_VSOCK fuzzing executor: add syz_vhost_vsock_setup and syz_vhost_vsock_send Oct 30, 2026
@roglew

roglew commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Here is what changed.

The descriptions commit is now its own PR, #8086. It also takes the sendmsg comment: sendmsg$vsock_stream uses a vsock-specific send_msghdr_vsock with msg_name and msg_namelen fixed to 0, because vsock_connectible_sendmsg() returns -EISCONN or -EOPNOTSUPP whenever msg_namelen is set.

You were right that the guest-side setup gets little without a peer, so this PR is now only the host-side approach you suggested. syz_vhost_vsock_setup(cid) opens /dev/vhost-vsock, identity-maps the rx and tx virtqueues plus one buffer per descriptor with VHOST_SET_MEM_TABLE, sets the guest CID and starts the device. syz_vhost_vsock_send(fd, pkt, len) puts one virtio_vsock_hdr + payload on the tx ring, kicks it, and hands used rx buffers back so host replies keep flowing.

The executor fills in src_cid and dst_cid, so every generated packet passes the host's address check. The rest of the header (ports, op, type, flags, credit) and the payload are a typed syzlang struct. The guest CID is the vmaddr_cid my per-proc range, so host AF_VSOCK sockets can also connect to the emulated guest.

The seed sys/linux/test/vsock_vhost_guest does a guest REQUEST, host accept4, guest RW, host recvfrom and sendto, guest SHUTDOWN, and then a host connect that the guest completes with RESPONSE.

Coverage was measured with KCOV on Linux 6.18 using syz-execprog -coverfile, the same method for every row. "Before" is the existing ioctl-only vhost seed, which only drives the /dev/vhost-vsock setup surface.

file before after (one seed) newly reached
drivers/vhost/vsock.c 26/147 PCs 49/147 vhost_vsock_handle_tx_kick, vhost_vsock_alloc_skb, vhost_vsock_handle_rx_kick, vhost_transport_do_send_pkt reply path
net/vmw_vsock/virtio_transport_common.c 34/284 95/284 virtio_transport_recv_pkt/_listen/_connected/_connecting/_enqueue, send_response, reset_no_sock, space_update, do_close
net/vmw_vsock/af_vsock.c 52/421 110/421 vsock_enqueue_accept, vsock_find_connected_socket, vsock_insert_connected, h2g vsock_assign_transport

This matches the gap on syzbot's coverage page for drivers/vhost/vsock.c. The ioctl and setup paths are covered there; the datapath from the tx kick through virtio_transport_recv_pkt to the replies in do_send_pkt is not, because nothing ever acts as the guest.

A 13-minute syz-manager run with only the vsock calls enabled (on the previous revision of this commit) reached 50/147 in drivers/vhost/vsock.c, 114/284 in virtio_transport_common.c and 170/421 in af_vsock.c. After about 5 minutes it hit WARNING in check_mm (kernel/fork.c, "non-zero pgtables_bytes on freeing mm", __mmdrop <- finish_task_switch) in a program containing syz_vhost_vsock_setup. It looks like a vhost worker racing process exit; no reproducer yet. The vhost datapath runs in the vhost worker, so it only shows up with remote_cover, which is the default.

The guest-side commits (QEMU vsock option and syz_vsock_virtio_reprobe) are out of this PR. I still think they are useful for instances that opt in. virtio_vsock_probe/remove and the teardown races are only reachable from the guest side. With the device present, the host's vhost does answer with RST, RESPONSE and credit traffic, and that drives the guest's virtio_transport.c workers. I have no coverage numbers for them yet, so I will measure first and send a follow-up PR if the numbers justify it.

Your other two comments are addressed on that branch. TestRandomVsockCID is gone. The device name is per arch: vhost-vsock-ccw on s390x, vhost-vsock-device on arm, vhost-vsock-pci elsewhere, checked against QEMU's hw/virtio/vhost-vsock-pci.c, hw/s390x/vhost-vsock-ccw.c and include/hw/virtio/vhost-vsock.h.

@roglew

roglew commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

In case the above comment didn't make it obvious enough, I had an agent take a swing at the revisions. If anything seems weird, if anything ended up too long or annoying to review, or if I should take a closer manual look at anything let me know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

well-shaped PR has no conflicts and all checks pass

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants