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

Reply via email to