> -----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.
> + 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
>