On Tue, Oct 20, 2026 at 2:27 PM Thanos Makatos
<[email protected]> wrote:
>
>
>
> > -----Original Message-----
> > From: [email protected] <qemu-
> > [email protected]> On Behalf Of
> > Naman Gulati
> > Sent: 14 October 2026 22:11
> > To: [email protected]
> > Cc: John Levon <[email protected]>; Thanos Makatos
> > <[email protected]>; Cédric Le Goater <[email protected]>; Alex
> > Williamson <[email protected]>; Pierrick Bouvier
> > <[email protected]>; Matt Evans <[email protected]>;
> > Naman Gulati <[email protected]>
> > Subject: [PATCH v5 4/4] vfio-user: support multiple FDs for sparse
> > mmap regions
> >
> > !-------------------------------------------------------------------|
> > CAUTION: External Email
> >
> > |-------------------------------------------------------------------!
> >
> > Add client support for capability VFIO_REGION_INFO_CAP_SPARSE_MMAP_FDS
> > (capability ID 0x8001) in vfio-user, and advertise support via the
> > "sparse_mmap_fds" capability in VFIO_USER_VERSION.
> >
> > Update vfio_user_device_io_get_region_info() to receive multiple file
> > descriptors passed in SCM_RIGHTS ancillary data for a region, store
> > them
> > in VFIOUserProxy, and implement setup_sparse_mmaps in
> > vfio_user_device_io_ops_sock to validate and populate per-area file
> > descriptors and fd_offset values.
> >
> > Signed-off-by: Naman Gulati <[email protected]>
> > ---
> > hw/vfio-user/protocol.h | 35 ++++++++++--
> > hw/vfio-user/proxy.h | 2 +
> > hw/vfio-user/device.c | 121 +++++++++++++++++++++++++++++++++++++--
> > -
> > hw/vfio-user/proxy.c | 52 +++++++++++++++--
> > 4 files changed, 193 insertions(+), 17 deletions(-)
> >
> > diff --git a/hw/vfio-user/protocol.h b/hw/vfio-user/protocol.h
> > index c478d1353f..b823689254 100644
> > --- a/hw/vfio-user/protocol.h
> > +++ b/hw/vfio-user/protocol.h
> > @@ -15,6 +15,8 @@
> > * SPDX-License-Identifier: GPL-2.0-or-later
> > */
> >
> > +#define VFIO_USER_MAX_REGIONS 100
> > +
> > typedef struct {
> > uint16_t id;
> > uint16_t command;
> > @@ -69,12 +71,13 @@ typedef struct {
> > #define VFIO_USER_CAP "capabilities"
> >
> > /* "capabilities" members */
> > -#define VFIO_USER_CAP_MAX_FDS "max_msg_fds"
> > -#define VFIO_USER_CAP_MAX_XFER "max_data_xfer_size"
> > -#define VFIO_USER_CAP_PGSIZES "pgsizes"
> > -#define VFIO_USER_CAP_MAP_MAX "max_dma_maps"
> > -#define VFIO_USER_CAP_MIGR "migration"
> > -#define VFIO_USER_CAP_MULTI "write_multiple"
> > +#define VFIO_USER_CAP_MAX_FDS "max_msg_fds"
> > +#define VFIO_USER_CAP_MAX_XFER "max_data_xfer_size"
> > +#define VFIO_USER_CAP_PGSIZES "pgsizes"
> > +#define VFIO_USER_CAP_MAP_MAX "max_dma_maps"
> > +#define VFIO_USER_CAP_MIGR "migration"
> > +#define VFIO_USER_CAP_MULTI "write_multiple"
> > +#define VFIO_USER_CAP_SPARSE_MMAP_FDS "sparse_mmap_fds"
> >
> > /* "migration" members */
> > #define VFIO_USER_CAP_PGSIZE "pgsize"
> > @@ -166,6 +169,26 @@ typedef struct {
> > uint64_t offset;
> > } VFIOUserRegionInfo;
> >
> > +/*
> > + * VFIO_REGION_INFO_CAP_SPARSE_MMAP_FDS
> > + */
> > +#define VFIO_REGION_INFO_CAP_SPARSE_MMAP_FDS 0x8001
> > +
> > +struct vfio_region_sparse_mmap_fd_area {
> > + uint64_t offset;
> > + uint64_t fd_offset;
> > + uint64_t size;
> > + uint32_t fd_index;
> > + uint32_t pad;
> > +};
> > +
> > +struct vfio_region_info_cap_sparse_mmap_fds {
> > + struct vfio_info_cap_header header;
> > + uint32_t nr_areas;
> > + uint32_t reserved;
> > + struct vfio_region_sparse_mmap_fd_area areas[];
> > +};
> > +
> > /*
> > * VFIO_USER_DEVICE_GET_IRQ_INFO
> > * imported from struct vfio_irq_info
> > diff --git a/hw/vfio-user/proxy.h b/hw/vfio-user/proxy.h
> > index 7b97460cc5..4b9834a436 100644
> > --- a/hw/vfio-user/proxy.h
> > +++ b/hw/vfio-user/proxy.h
> > @@ -72,6 +72,7 @@ typedef struct VFIOUserProxy {
> > AioContext *ctx;
> > QEMUBH *req_bh;
> > bool async_ops;
> > + VFIOUserFDs *region_fds[VFIO_USER_MAX_REGIONS];
> >
> > /*
> > * above only changed when BQL is held
> > @@ -111,6 +112,7 @@ bool vfio_user_validate_version(VFIOUserProxy
> > *proxy, Error **errp);
> >
> > VFIOUserFDs *vfio_user_getfds(int numfds);
> > void vfio_user_putfds(VFIOUserMsg *msg);
> > +void vfio_user_free_fds(VFIOUserFDs *fds);
> >
> > void vfio_user_disable_posted_writes(VFIOUserProxy *proxy);
> >
> > diff --git a/hw/vfio-user/device.c b/hw/vfio-user/device.c
> > index 1a01e748cd..4fbf536a04 100644
> > --- a/hw/vfio-user/device.c
> > +++ b/hw/vfio-user/device.c
> > @@ -14,12 +14,14 @@
> >
> > #include "hw/vfio-user/device.h"
> > #include "hw/vfio-user/trace.h"
> > +#include "hw/vfio/vfio-region.h"
> > +#include "hw/vfio/vfio-helpers.h"
> > +#include "hw/vfio/trace.h"
> >
> > /*
> > * These are to defend against a malign server trying
> > * to force us to run out of memory.
> > */
> > -#define VFIO_USER_MAX_REGIONS 100
> > #define VFIO_USER_MAX_IRQS 50
> >
> > bool vfio_user_get_device_info(VFIOUserProxy *proxy,
> > @@ -209,14 +211,17 @@ static int
> > vfio_user_device_io_get_region_info(VFIODevice *vbasedev,
> > struct
> > vfio_region_info *info,
> > int *fd)
> > {
> > - VFIOUserFDs fds = { 0, 1, fd};
> > - int ret;
> > + int fds[VFIO_USER_MAX_MAX_FDS];
> > + VFIOUserFDs user_fds = { 0, VFIO_USER_MAX_MAX_FDS, fds };
> > + int i, ret;
> > +
> > + *fd = -1;
> >
> > if (info->index > vbasedev->num_initial_regions) {
> > return -EINVAL;
> > }
> >
> > - ret = vfio_user_get_region_info(vbasedev->proxy, info, &fds);
> > + ret = vfio_user_get_region_info(vbasedev->proxy, info,
> > &user_fds);
> > if (ret) {
> > return ret;
> > }
> > @@ -225,10 +230,30 @@ static int
> > vfio_user_device_io_get_region_info(VFIODevice *vbasedev,
> > if ((info->flags & VFIO_REGION_INFO_FLAG_CAPS) &&
> > (info->cap_offset < sizeof(*info)
> > || info->cap_offset + sizeof(struct vfio_info_cap_header) >
> > info->argsz)) {
> > - return -EINVAL;
> > + ret = -EINVAL;
> > + } else if (vfio_get_region_info_cap(info,
> > +
> > VFIO_REGION_INFO_CAP_SPARSE_MMAP_FDS)) {
> > + if (user_fds.recv_fds > 0) {
> > + VFIOUserFDs *saved = vfio_user_getfds(user_fds.recv_fds);
> > +
> > + saved->recv_fds = user_fds.recv_fds;
> > + memcpy(saved->fds, fds, user_fds.recv_fds * sizeof(int));
> > + vfio_user_free_fds(vbasedev->proxy->region_fds[info-
> > >index]);
> > + vbasedev->proxy->region_fds[info->index] = saved;
> > + return 0;
> > + }
> > + ret = -EINVAL;
> > + } else if (user_fds.recv_fds > 0) {
> > + *fd = fds[0];
> > + fds[0] = -1;
> > }
> >
> > - return 0;
> > + for (i = 0; i < user_fds.recv_fds; i++) {
> > + if (fds[i] >= 0) {
> > + close(fds[i]);
> > + }
> > + }
> > + return ret;
> > }
> >
> > static int vfio_user_device_io_get_irq_info(VFIODevice *vbasedev,
> > @@ -518,12 +543,94 @@ static int
> > vfio_user_device_io_region_write(VFIODevice *vbasedev, uint8_t index,
> > /*
> > * Socket-based io_ops
> > */
> > +static int vfio_user_device_io_setup_sparse_mmaps(VFIORegion *region,
> > + struct
> > vfio_region_info *info,
> > + Error **errp)
> > +{
> > + struct vfio_info_cap_header *hdr;
> > + struct vfio_region_info_cap_sparse_mmap_fds *sparse_fds;
> > + VFIOUserFDs *user_fds;
> > + size_t cap_offset;
> > + int i;
> > +
> > + hdr = vfio_get_region_info_cap(info,
> > VFIO_REGION_INFO_CAP_SPARSE_MMAP_FDS);
> > + if (!hdr) {
> > + return vfio_default_setup_sparse_mmaps(region, info, errp);
> > + }
> > +
> > + if (vfio_get_region_info_cap(info,
> > VFIO_REGION_INFO_CAP_SPARSE_MMAP)) {
> > + error_setg(errp, "%s: region %d cannot specify both "
> > + "SPARSE_MMAP and SPARSE_MMAP_FDS", __func__,
> > region->nr);
> > + return -EINVAL;
> > + }
> > +
> > + sparse_fds = container_of(hdr, struct
> > vfio_region_info_cap_sparse_mmap_fds,
> > + header);
> > +
> > + cap_offset = (uintptr_t)hdr - (uintptr_t)info;
> > + if (cap_offset > info->argsz ||
> > + info->argsz - cap_offset < sizeof(*sparse_fds) ||
> > + sparse_fds->nr_areas >
> > + (info->argsz - cap_offset - sizeof(*sparse_fds)) /
> > + sizeof(sparse_fds->areas[0])) {
> > + error_setg(errp, "%s: invalid nr_areas %u in region %d",
> > + __func__, sparse_fds->nr_areas, region->nr);
> > + return -EINVAL;
> > + }
> > +
> > + trace_vfio_region_sparse_mmap_header(region->vbasedev->name,
> > + region->nr, sparse_fds-
> > >nr_areas);
> > +
> > + user_fds = region->vbasedev->proxy->region_fds[region->nr];
> > + if (!user_fds || user_fds->recv_fds > sparse_fds->nr_areas) {
> > + error_setg(errp, "%s: invalid fd count %d for %u sparse mmap
> > areas "
> > + "in region %d", __func__,
> > + user_fds ? user_fds->recv_fds : 0,
> > + sparse_fds->nr_areas, region->nr);
> > + return -EINVAL;
> > + }
> > +
> > + region->mmaps = g_try_new0(VFIOMmap, sparse_fds->nr_areas);
>
> IMO we should impose a limit on sparse_fds->nr_areas, g_try_new0 does prevent
> the server from crashing but a malicious client can allocate too much memory.
sparse_fds->nr_areas is already bounded by info->argsz via the check just above:
if (cap_offset > info->argsz ||
info->argsz - cap_offset < sizeof(*sparse_fds) ||
sparse_fds->nr_areas >
(info->argsz - cap_offset - sizeof(*sparse_fds)) /
sizeof(sparse_fds->areas[0])) {
error_setg(errp, "%s: invalid nr_areas %u in region %d",
__func__, sparse_fds->nr_areas, region->nr);
return -EINVAL;
}
And in vfio_user_get_region_info(), info->argsz is capped by
proxy->max_xfer_size (at most VFIO_USER_DEF_MAX_XFER = 1 MiB), so
sparse_fds->nr_areas cannot exceed ~32K entries (the same bound that
applies to VFIO_REGION_INFO_CAP_SPARSE_MMAP in
vfio_region_setup_sparse_mmaps()).
Does that existing bound look sufficient to you, or would you still
prefer an explicit smaller limit (e.g. a VFIO_USER_MAX_SPARSE_MMAPS
constant alongside VFIO_USER_MAX_REGIONS)?
>
> > + if (!region->mmaps) {
> > + error_setg(errp, "%s: failed to allocate %u mmaps for region
> > %d",
> > + __func__, sparse_fds->nr_areas, region->nr);
> > + return -ENOMEM;
> > + }
> > +
> > + for (i = 0; i < sparse_fds->nr_areas; i++) {
> > + struct vfio_region_sparse_mmap_fd_area *area = &sparse_fds-
> > >areas[i];
> > +
> > + if (area->size == 0 || area->offset > region->size ||
> > + area->size > region->size - area->offset ||
> > + area->fd_index >= user_fds->recv_fds) {
> > + error_setg(errp, "%s: invalid sparse mmap area %d in
> > region %d "
> > + "(offset=0x%"PRIx64" size=0x%"PRIx64
> > + " fd_offset=0x%"PRIx64" fd_index=%u)",
> > + __func__, i, region->nr, area->offset, area-
> > >size,
> > + area->fd_offset, area->fd_index);
> > + g_free(region->mmaps);
> > + region->mmaps = NULL;
> > + return -EINVAL;
> > + }
> > +
> > + trace_vfio_region_sparse_mmap_entry(i, area->offset,
> > + area->offset + area->size
> > - 1);
> > + region->mmaps[i].offset = area->offset;
> > + region->mmaps[i].fd_offset = area->fd_offset;
> > + region->mmaps[i].size = area->size;
> > + region->mmaps[i].fd = user_fds->fds[area->fd_index];
> > + }
> > +
> > + region->nr_mmaps = sparse_fds->nr_areas;
> > + return 0;
> > +}
> > +
> > VFIODeviceIOOps vfio_user_device_io_ops_sock = {
> > .device_feature = vfio_user_device_io_device_feature,
> > .get_region_info = vfio_user_device_io_get_region_info,
> > + .setup_sparse_mmaps = vfio_user_device_io_setup_sparse_mmaps,
> > .get_irq_info = vfio_user_device_io_get_irq_info,
> > .set_irqs = vfio_user_device_io_set_irqs,
> > .region_read = vfio_user_device_io_region_read,
> > .region_write = vfio_user_device_io_region_write,
> > -
> > };
> > diff --git a/hw/vfio-user/proxy.c b/hw/vfio-user/proxy.c
> > index 197aee07bf..ba29085321 100644
> > --- a/hw/vfio-user/proxy.c
> > +++ b/hw/vfio-user/proxy.c
> > @@ -363,6 +363,13 @@ static int vfio_user_recv_one(VFIOUserProxy
> > *proxy, Error **errp)
> > }
> >
> > memcpy(msg->fds->fds, fdp, numfds * sizeof(int));
> > + } else if (msg->fds != NULL) {
> > + /*
> > + * recv_fds holds the capacity of msg->fds->fds on entry;
> > reset it
> > + * to 0 when the reply carries no FDs so the caller knows
> > none were
> > + * received.
> > + */
> > + msg->fds->recv_fds = 0;
> > }
> >
> > /*
> > @@ -740,6 +747,11 @@ bool vfio_user_send_wait(VFIOUserProxy *proxy,
> > VFIOUserHdr *hdr,
> >
> > qemu_mutex_unlock(&proxy->lock);
> >
> > + if (!ok && fds != NULL) {
> > + /* No reply was received; report 0 received FDs to the
> > caller. */
> > + fds->recv_fds = 0;
> > + }
> > +
> > return ok;
> > }
> >
> > @@ -867,17 +879,30 @@ void vfio_user_send_error(VFIOUserProxy *proxy,
> > VFIOUserHdr *hdr, int error)
> > }
> >
> > /*
> > - * Close FDs erroneously received in an incoming request.
> > + * Close and free received FDs.
> > */
> > -void vfio_user_putfds(VFIOUserMsg *msg)
> > +void vfio_user_free_fds(VFIOUserFDs *fds)
> > {
> > - VFIOUserFDs *fds = msg->fds;
> > int i;
> >
> > + if (!fds) {
> > + return;
> > + }
> > +
> > for (i = 0; i < fds->recv_fds; i++) {
> > - close(fds->fds[i]);
> > + if (fds->fds[i] >= 0) {
> > + close(fds->fds[i]);
> > + }
> > }
> > g_free(fds);
> > +}
> > +
> > +/*
> > + * Close FDs erroneously received in an incoming request.
> > + */
> > +void vfio_user_putfds(VFIOUserMsg *msg)
> > +{
> > + vfio_user_free_fds(msg->fds);
> > msg->fds = NULL;
> > }
> >
> > @@ -967,6 +992,7 @@ void vfio_user_set_handler(VFIODevice *vbasedev,
> > void vfio_user_disconnect(VFIOUserProxy *proxy)
> > {
> > VFIOUserMsg *r1, *r2;
> > + int i;
> >
> > qemu_mutex_lock(&proxy->lock);
> >
> > @@ -1027,6 +1053,10 @@ void vfio_user_disconnect(VFIOUserProxy *proxy)
> > vfio_user_iothread = NULL;
> > }
> >
> > + for (i = 0; i < VFIO_USER_MAX_REGIONS; i++) {
> > + vfio_user_free_fds(proxy->region_fds[i]);
> > + }
> > +
> > g_free(proxy->sockname);
> > g_free(proxy);
> > }
> > @@ -1214,6 +1244,18 @@ static bool check_multi(VFIOUserProxy *proxy,
> > QObject *qobj, Error **errp)
> > return true;
> > }
> >
> > +static bool check_sparse_mmap_fds(VFIOUserProxy *proxy, QObject
> > *qobj,
> > + Error **errp)
> > +{
> > + QBool *qb = qobject_to(QBool, qobj);
> > +
> > + if (qb == NULL) {
> > + error_setg(errp, "malformed %s",
> > VFIO_USER_CAP_SPARSE_MMAP_FDS);
> > + return false;
> > + }
> > + return true;
> > +}
> > +
> > static struct cap_entry caps_cap[] = {
> > { VFIO_USER_CAP_MAX_FDS, check_max_fds },
> > { VFIO_USER_CAP_MAX_XFER, check_max_xfer },
> > @@ -1221,6 +1263,7 @@ static struct cap_entry caps_cap[] = {
> > { VFIO_USER_CAP_MAP_MAX, check_max_dma },
> > { VFIO_USER_CAP_MIGR, check_migr },
> > { VFIO_USER_CAP_MULTI, check_multi },
> > + { VFIO_USER_CAP_SPARSE_MMAP_FDS, check_sparse_mmap_fds },
> > { NULL }
> > };
> >
> > @@ -1280,6 +1323,7 @@ static GString *caps_json(void)
> > qdict_put_int(capdict, VFIO_USER_CAP_PGSIZES,
> > VFIO_USER_DEF_PGSIZE);
> > qdict_put_int(capdict, VFIO_USER_CAP_MAP_MAX,
> > VFIO_USER_DEF_MAP_MAX);
> > qdict_put_bool(capdict, VFIO_USER_CAP_MULTI, true);
> > + qdict_put_bool(capdict, VFIO_USER_CAP_SPARSE_MMAP_FDS, true);
> >
> > qdict_put_obj(dict, VFIO_USER_CAP, QOBJECT(capdict));
> >
> > --
> > 2.56.0.385.gd3acb90ef8-goog
> >
>