Skip to content

fuse: virtio_fs: clamp max_pages_limit by the transport DMA mapping size#261

Draft
benhillis wants to merge 1 commit into
linux-msft-wsl-6.18.yfrom
user/benhill/virtiofs-clamp-max-pages-dma
Draft

fuse: virtio_fs: clamp max_pages_limit by the transport DMA mapping size#261
benhillis wants to merge 1 commit into
linux-msft-wsl-6.18.yfrom
user/benhill/virtiofs-clamp-max-pages-dma

Conversation

@benhillis

@benhillis benhillis commented Jun 9, 2026

Copy link
Copy Markdown
Member

Summary

virtio-fs negotiates max_pages (and therefore max_write) purely from the FUSE_INIT reply, without accounting for the largest single DMA mapping the transport can perform. When the device sits behind a bouncing DMA layer — e.g. swiotlb=force, which WSL enables whenever a virtio-fs device is present — the effective per-mapping limit is IO_TLB_SEGSIZE * IO_TLB_SIZE = 256 KiB. Combined with the FUSE default of 256 pages (1 MiB max_write), the guest builds read/readdir/write requests whose buffers cannot be DMA-mapped, failing with -EIO and wedging the request virtqueue.

virtio-blk already avoids this whole class of bug by clamping its transfer size with virtio_max_dma_size() (see drivers/block/virtio_blk.c, which bounds max_segment_size the same way). This change does the same for virtio-fs: it lowers the connection's max_pages_limit to the transport's per-mapping DMA limit, right next to the existing virtqueue-size clamp in virtio_fs_get_tree(). process_init_reply() then caps the negotiated max_pages (and transitively max_write) to a value the guest can always map. When no DMA limit applies, virtio_max_dma_size() returns SIZE_MAX and the clamp is a no-op — zero change for non-bounce-buffered guests.

The change

/* existing: bound by the request virtqueue size */
fc->max_pages_limit = min_t(unsigned int, fc->max_pages_limit,
                            virtqueue_size - FUSE_HEADER_OVERHEAD);

/* new: also bound by the transport's max single DMA mapping size */
fc->max_pages_limit = min_t(unsigned int, fc->max_pages_limit,
                            virtio_max_dma_size(fs->vqs[VQ_REQUEST].vq->vdev)
                                >> PAGE_SHIFT);

Under swiotlb=force this drops max_pages_limit to 64 → negotiated max_write of 256 KiB, which the guest can always bounce.

Testing

Built linux-msft-wsl-6.18.y + this patch with Microsoft/config-wsl (CONFIG_VIRTIO_FS=y); boots as 6.18.33.1-microsoft-standard-WSL2+.

Validated against a virtio-fs server offering the default 1 MiB max_write, with swiotlb=force active. The only variable between runs is the guest kernel:

Guest kernel DrvFsTests::WSL2VirtioFs GetDents
stock (no clamp) -EIO, wedged request virtqueue
this patch PASS

With the clamp, the negotiated max_pages drops to 64 (256 KiB max_write), so readdir/read/write buffers always fit the swiotlb per-mapping limit.

virtio-fs negotiates max_pages (and therefore max_write) without regard
for the maximum size of a single DMA mapping the transport can perform.
When the device sits behind a bouncing DMA layer -- e.g. swiotlb=force,
which WSL enables whenever a virtio-fs device is present -- the effective
per-mapping limit is IO_TLB_SEGSIZE * IO_TLB_SIZE = 256 KiB. Combined
with the FUSE default of 256 pages (1 MiB max_write), the guest builds
read/readdir/write requests whose buffers cannot be DMA-mapped, which
fails with -EIO and wedges the request virtqueue.

virtio-blk already avoids this class of bug by clamping its transfer
size with virtio_max_dma_size(). Do the same here: lower the
connection's max_pages_limit to the transport's per-mapping DMA limit,
right next to the existing virtqueue-size clamp. process_init_reply()
then caps the negotiated max_pages (and transitively max_write) to a
value the guest can always map. When no DMA limit applies,
virtio_max_dma_size() returns SIZE_MAX and the clamp is a no-op.

Signed-off-by: Ben Hillis <benhill@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants