Message ID | a14c2c09f41ddda83cd710516cac8d210ec9db08.1629131628.git.elena.ufimtseva@oracle.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | vfio-user implementation | expand |
On Mon, Aug 16, 2021 at 09:42:39AM -0700, Elena Ufimtseva wrote: > diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c > index 7005d9f891..eae33e746f 100644 > --- a/hw/vfio/pci.c > +++ b/hw/vfio/pci.c > @@ -3397,6 +3397,12 @@ static void vfio_user_pci_realize(PCIDevice *pdev, Error **errp) > proxy->flags |= VFIO_PROXY_SECURE; > } > > + vfio_user_validate_version(vbasedev, &err); > + if (err != NULL) { > + error_propagate(errp, err); > + goto error; > + } > + > vbasedev->name = g_strdup_printf("VFIO user <%s>", udev->sock_name); > vbasedev->dev = DEVICE(vdev); > vbasedev->fd = -1; > @@ -3404,6 +3410,9 @@ static void vfio_user_pci_realize(PCIDevice *pdev, Error **errp) > vbasedev->no_mmap = false; > vbasedev->ops = &vfio_user_pci_ops; > > +error: Missing return before error label? We shouldn't disconnect in the success case. > + vfio_user_disconnect(proxy); > + error_prepend(errp, VFIO_MSG_PREFIX, vdev->vbasedev.name); > } > > static void vfio_user_instance_finalize(Object *obj) > diff --git a/hw/vfio/user.c b/hw/vfio/user.c > index 2fcc77d997..e89464a571 100644 > --- a/hw/vfio/user.c > +++ b/hw/vfio/user.c > @@ -23,9 +23,16 @@ > #include "io/channel-socket.h" > #include "io/channel-util.h" > #include "sysemu/iothread.h" > +#include "qapi/qmp/qdict.h" > +#include "qapi/qmp/qjson.h" > +#include "qapi/qmp/qnull.h" > +#include "qapi/qmp/qstring.h" > +#include "qapi/qmp/qnum.h" > #include "user.h" > > static uint64_t max_xfer_size = VFIO_USER_DEF_MAX_XFER; > +static uint64_t max_send_fds = VFIO_USER_DEF_MAX_FDS; > +static int wait_time = 1000; /* wait 1 sec for replies */ > static IOThread *vfio_user_iothread; > > static void vfio_user_shutdown(VFIOProxy *proxy); > @@ -34,7 +41,14 @@ static void vfio_user_send_locked(VFIOProxy *proxy, VFIOUserHdr *msg, > VFIOUserFDs *fds); > static void vfio_user_send(VFIOProxy *proxy, VFIOUserHdr *msg, > VFIOUserFDs *fds); > +static void vfio_user_request_msg(VFIOUserHdr *hdr, uint16_t cmd, > + uint32_t size, uint32_t flags); > +static void vfio_user_send_recv(VFIOProxy *proxy, VFIOUserHdr *msg, > + VFIOUserFDs *fds, int rsize, int flags); > > +/* vfio_user_send_recv flags */ > +#define NOWAIT 0x1 /* do not wait for reply */ > +#define NOIOLOCK 0x2 /* do not drop iolock */ Please use "BQL", it's a widely used term while "iolock" isn't used: s/IOLOCK/BQL/ > > /* > * Functions called by main, CPU, or iothread threads > @@ -333,6 +347,79 @@ static void vfio_user_cb(void *opaque) > * Functions called by main or CPU threads > */ > > +static void vfio_user_send_recv(VFIOProxy *proxy, VFIOUserHdr *msg, > + VFIOUserFDs *fds, int rsize, int flags) > +{ > + VFIOUserReply *reply; > + bool iolock = 0; > + > + if (msg->flags & VFIO_USER_NO_REPLY) { > + error_printf("vfio_user_send_recv on async message\n"); > + return; > + } > + > + /* > + * We may block later, so use a per-proxy lock and let > + * the iothreads run while we sleep unless told no to s/no/not/ > +int vfio_user_validate_version(VFIODevice *vbasedev, Error **errp) > +{ > + g_autofree VFIOUserVersion *msgp; > + GString *caps; > + int size, caplen; > + > + caps = caps_json(); > + caplen = caps->len + 1; > + size = sizeof(*msgp) + caplen; > + msgp = g_malloc0(size); > + > + vfio_user_request_msg(&msgp->hdr, VFIO_USER_VERSION, size, 0); > + msgp->major = VFIO_USER_MAJOR_VER; > + msgp->minor = VFIO_USER_MINOR_VER; > + memcpy(&msgp->capabilities, caps->str, caplen); > + g_string_free(caps, true); > + > + vfio_user_send_recv(vbasedev->proxy, &msgp->hdr, NULL, 0, 0); > + if (msgp->hdr.flags & VFIO_USER_ERROR) { > + error_setg_errno(errp, msgp->hdr.error_reply, "version reply"); > + return -1; > + } > + > + if (msgp->major != VFIO_USER_MAJOR_VER || > + msgp->minor > VFIO_USER_MINOR_VER) { > + error_setg(errp, "incompatible server version"); > + return -1; > + } > + if (caps_check(msgp->minor, (char *)msgp + sizeof(*msgp), errp) != 0) { The reply is untrusted so we cannot treat it as a NUL-terminated string yet. The final byte msgp->capabilities[] needs to be checked first. Please be careful about input validation, I might miss something so it's best if you audit the patches too. QEMU must not trust the device emulation process and vice versa. > + return -1; > + } > + > + return 0; > +} > -- > 2.25.1 >
> On Aug 24, 2021, at 8:59 AM, Stefan Hajnoczi <stefanha@redhat.com> wrote: > > On Mon, Aug 16, 2021 at 09:42:39AM -0700, Elena Ufimtseva wrote: >> diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c >> index 7005d9f891..eae33e746f 100644 >> --- a/hw/vfio/pci.c >> +++ b/hw/vfio/pci.c >> @@ -3397,6 +3397,12 @@ static void vfio_user_pci_realize(PCIDevice *pdev, Error **errp) >> proxy->flags |= VFIO_PROXY_SECURE; >> } >> >> + vfio_user_validate_version(vbasedev, &err); >> + if (err != NULL) { >> + error_propagate(errp, err); >> + goto error; >> + } >> + >> vbasedev->name = g_strdup_printf("VFIO user <%s>", udev->sock_name); >> vbasedev->dev = DEVICE(vdev); >> vbasedev->fd = -1; >> @@ -3404,6 +3410,9 @@ static void vfio_user_pci_realize(PCIDevice *pdev, Error **errp) >> vbasedev->no_mmap = false; >> vbasedev->ops = &vfio_user_pci_ops; >> >> +error: > > Missing return before error label? We shouldn't disconnect in the > success case. > The return ended up in a later patch. >> + vfio_user_disconnect(proxy); >> + error_prepend(errp, VFIO_MSG_PREFIX, vdev->vbasedev.name); >> } >> >> static void vfio_user_instance_finalize(Object *obj) >> diff --git a/hw/vfio/user.c b/hw/vfio/user.c >> index 2fcc77d997..e89464a571 100644 >> --- a/hw/vfio/user.c >> +++ b/hw/vfio/user.c >> @@ -23,9 +23,16 @@ >> #include "io/channel-socket.h" >> #include "io/channel-util.h" >> #include "sysemu/iothread.h" >> +#include "qapi/qmp/qdict.h" >> +#include "qapi/qmp/qjson.h" >> +#include "qapi/qmp/qnull.h" >> +#include "qapi/qmp/qstring.h" >> +#include "qapi/qmp/qnum.h" >> #include "user.h" >> >> static uint64_t max_xfer_size = VFIO_USER_DEF_MAX_XFER; >> +static uint64_t max_send_fds = VFIO_USER_DEF_MAX_FDS; >> +static int wait_time = 1000; /* wait 1 sec for replies */ >> static IOThread *vfio_user_iothread; >> >> static void vfio_user_shutdown(VFIOProxy *proxy); >> @@ -34,7 +41,14 @@ static void vfio_user_send_locked(VFIOProxy *proxy, VFIOUserHdr *msg, >> VFIOUserFDs *fds); >> static void vfio_user_send(VFIOProxy *proxy, VFIOUserHdr *msg, >> VFIOUserFDs *fds); >> +static void vfio_user_request_msg(VFIOUserHdr *hdr, uint16_t cmd, >> + uint32_t size, uint32_t flags); >> +static void vfio_user_send_recv(VFIOProxy *proxy, VFIOUserHdr *msg, >> + VFIOUserFDs *fds, int rsize, int flags); >> >> +/* vfio_user_send_recv flags */ >> +#define NOWAIT 0x1 /* do not wait for reply */ >> +#define NOIOLOCK 0x2 /* do not drop iolock */ > > Please use "BQL", it's a widely used term while "iolock" isn't used: > s/IOLOCK/BQL/ > OK >> >> /* >> * Functions called by main, CPU, or iothread threads >> @@ -333,6 +347,79 @@ static void vfio_user_cb(void *opaque) >> * Functions called by main or CPU threads >> */ >> >> +static void vfio_user_send_recv(VFIOProxy *proxy, VFIOUserHdr *msg, >> + VFIOUserFDs *fds, int rsize, int flags) >> +{ >> + VFIOUserReply *reply; >> + bool iolock = 0; >> + >> + if (msg->flags & VFIO_USER_NO_REPLY) { >> + error_printf("vfio_user_send_recv on async message\n"); >> + return; >> + } >> + >> + /* >> + * We may block later, so use a per-proxy lock and let >> + * the iothreads run while we sleep unless told no to > > s/no/not/ OK > >> +int vfio_user_validate_version(VFIODevice *vbasedev, Error **errp) >> +{ >> + g_autofree VFIOUserVersion *msgp; >> + GString *caps; >> + int size, caplen; >> + >> + caps = caps_json(); >> + caplen = caps->len + 1; >> + size = sizeof(*msgp) + caplen; >> + msgp = g_malloc0(size); >> + >> + vfio_user_request_msg(&msgp->hdr, VFIO_USER_VERSION, size, 0); >> + msgp->major = VFIO_USER_MAJOR_VER; >> + msgp->minor = VFIO_USER_MINOR_VER; >> + memcpy(&msgp->capabilities, caps->str, caplen); >> + g_string_free(caps, true); >> + >> + vfio_user_send_recv(vbasedev->proxy, &msgp->hdr, NULL, 0, 0); >> + if (msgp->hdr.flags & VFIO_USER_ERROR) { >> + error_setg_errno(errp, msgp->hdr.error_reply, "version reply"); >> + return -1; >> + } >> + >> + if (msgp->major != VFIO_USER_MAJOR_VER || >> + msgp->minor > VFIO_USER_MINOR_VER) { >> + error_setg(errp, "incompatible server version"); >> + return -1; >> + } >> + if (caps_check(msgp->minor, (char *)msgp + sizeof(*msgp), errp) != 0) { > > The reply is untrusted so we cannot treat it as a NUL-terminated string > yet. The final byte msgp->capabilities[] needs to be checked first. > > Please be careful about input validation, I might miss something so it's > best if you audit the patches too. QEMU must not trust the device > emulation process and vice versa. > This message is consumed by vfio-user, so I can check for valid replies, but for most messages this checking will have to be done at in the VFIO common or bus-specific code, as vfio-user doens’t know valid data from invalid. JJ >> + return -1; >> + } >> + >> + return 0; >> +} >> -- >> 2.25.1
On Mon, Aug 30, 2021 at 03:08:50AM +0000, John Johnson wrote: > > On Aug 24, 2021, at 8:59 AM, Stefan Hajnoczi <stefanha@redhat.com> wrote: > > On Mon, Aug 16, 2021 at 09:42:39AM -0700, Elena Ufimtseva wrote: > >> +int vfio_user_validate_version(VFIODevice *vbasedev, Error **errp) > >> +{ > >> + g_autofree VFIOUserVersion *msgp; > >> + GString *caps; > >> + int size, caplen; > >> + > >> + caps = caps_json(); > >> + caplen = caps->len + 1; > >> + size = sizeof(*msgp) + caplen; > >> + msgp = g_malloc0(size); > >> + > >> + vfio_user_request_msg(&msgp->hdr, VFIO_USER_VERSION, size, 0); > >> + msgp->major = VFIO_USER_MAJOR_VER; > >> + msgp->minor = VFIO_USER_MINOR_VER; > >> + memcpy(&msgp->capabilities, caps->str, caplen); > >> + g_string_free(caps, true); > >> + > >> + vfio_user_send_recv(vbasedev->proxy, &msgp->hdr, NULL, 0, 0); > >> + if (msgp->hdr.flags & VFIO_USER_ERROR) { > >> + error_setg_errno(errp, msgp->hdr.error_reply, "version reply"); > >> + return -1; > >> + } > >> + > >> + if (msgp->major != VFIO_USER_MAJOR_VER || > >> + msgp->minor > VFIO_USER_MINOR_VER) { > >> + error_setg(errp, "incompatible server version"); > >> + return -1; > >> + } > >> + if (caps_check(msgp->minor, (char *)msgp + sizeof(*msgp), errp) != 0) { > > > > The reply is untrusted so we cannot treat it as a NUL-terminated string > > yet. The final byte msgp->capabilities[] needs to be checked first. > > > > Please be careful about input validation, I might miss something so it's > > best if you audit the patches too. QEMU must not trust the device > > emulation process and vice versa. > > > > This message is consumed by vfio-user, so I can check for valid > replies, but for most messages this checking will have to be done at in > the VFIO common or bus-specific code, as vfio-user doens’t know valid > data from invalid. Good point. Today's VFIO code trusts the replies because they come from the kernel. Now that they can come from an untrusted source they must be validated and common VFIO code may need to change its trust model. Stefan
diff --git a/hw/vfio/user-protocol.h b/hw/vfio/user-protocol.h index 27062cb910..14b762d1ad 100644 --- a/hw/vfio/user-protocol.h +++ b/hw/vfio/user-protocol.h @@ -52,6 +52,29 @@ enum vfio_user_command { #define VFIO_USER_ERROR 0x20 +/* + * VFIO_USER_VERSION + */ +typedef struct { + VFIOUserHdr hdr; + uint16_t major; + uint16_t minor; + char capabilities[]; +} VFIOUserVersion; + +#define VFIO_USER_MAJOR_VER 0 +#define VFIO_USER_MINOR_VER 0 + +#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_MIGR "migration" + +/* "migration" member */ +#define VFIO_USER_CAP_PGSIZE "pgsize" + #define VFIO_USER_DEF_MAX_FDS 8 #define VFIO_USER_MAX_MAX_FDS 16 diff --git a/hw/vfio/user.h b/hw/vfio/user.h index 905e374e12..cab957ba7a 100644 --- a/hw/vfio/user.h +++ b/hw/vfio/user.h @@ -70,5 +70,6 @@ void vfio_user_set_reqhandler(VFIODevice *vbasdev, VFIOUserFDs *fds), void *reqarg); void vfio_user_send_reply(VFIOProxy *proxy, char *buf, int ret); +int vfio_user_validate_version(VFIODevice *vbasedev, Error **errp); #endif /* VFIO_USER_H */ diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index 7005d9f891..eae33e746f 100644 --- a/hw/vfio/pci.c +++ b/hw/vfio/pci.c @@ -3397,6 +3397,12 @@ static void vfio_user_pci_realize(PCIDevice *pdev, Error **errp) proxy->flags |= VFIO_PROXY_SECURE; } + vfio_user_validate_version(vbasedev, &err); + if (err != NULL) { + error_propagate(errp, err); + goto error; + } + vbasedev->name = g_strdup_printf("VFIO user <%s>", udev->sock_name); vbasedev->dev = DEVICE(vdev); vbasedev->fd = -1; @@ -3404,6 +3410,9 @@ static void vfio_user_pci_realize(PCIDevice *pdev, Error **errp) vbasedev->no_mmap = false; vbasedev->ops = &vfio_user_pci_ops; +error: + vfio_user_disconnect(proxy); + error_prepend(errp, VFIO_MSG_PREFIX, vdev->vbasedev.name); } static void vfio_user_instance_finalize(Object *obj) diff --git a/hw/vfio/user.c b/hw/vfio/user.c index 2fcc77d997..e89464a571 100644 --- a/hw/vfio/user.c +++ b/hw/vfio/user.c @@ -23,9 +23,16 @@ #include "io/channel-socket.h" #include "io/channel-util.h" #include "sysemu/iothread.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qmp/qjson.h" +#include "qapi/qmp/qnull.h" +#include "qapi/qmp/qstring.h" +#include "qapi/qmp/qnum.h" #include "user.h" static uint64_t max_xfer_size = VFIO_USER_DEF_MAX_XFER; +static uint64_t max_send_fds = VFIO_USER_DEF_MAX_FDS; +static int wait_time = 1000; /* wait 1 sec for replies */ static IOThread *vfio_user_iothread; static void vfio_user_shutdown(VFIOProxy *proxy); @@ -34,7 +41,14 @@ static void vfio_user_send_locked(VFIOProxy *proxy, VFIOUserHdr *msg, VFIOUserFDs *fds); static void vfio_user_send(VFIOProxy *proxy, VFIOUserHdr *msg, VFIOUserFDs *fds); +static void vfio_user_request_msg(VFIOUserHdr *hdr, uint16_t cmd, + uint32_t size, uint32_t flags); +static void vfio_user_send_recv(VFIOProxy *proxy, VFIOUserHdr *msg, + VFIOUserFDs *fds, int rsize, int flags); +/* vfio_user_send_recv flags */ +#define NOWAIT 0x1 /* do not wait for reply */ +#define NOIOLOCK 0x2 /* do not drop iolock */ /* * Functions called by main, CPU, or iothread threads @@ -333,6 +347,79 @@ static void vfio_user_cb(void *opaque) * Functions called by main or CPU threads */ +static void vfio_user_send_recv(VFIOProxy *proxy, VFIOUserHdr *msg, + VFIOUserFDs *fds, int rsize, int flags) +{ + VFIOUserReply *reply; + bool iolock = 0; + + if (msg->flags & VFIO_USER_NO_REPLY) { + error_printf("vfio_user_send_recv on async message\n"); + return; + } + + /* + * We may block later, so use a per-proxy lock and let + * the iothreads run while we sleep unless told no to + */ + QEMU_LOCK_GUARD(&proxy->lock); + if (!(flags & NOIOLOCK)) { + iolock = qemu_mutex_iothread_locked(); + if (iolock) { + qemu_mutex_unlock_iothread(); + } + } + + reply = QTAILQ_FIRST(&proxy->free); + if (reply != NULL) { + QTAILQ_REMOVE(&proxy->free, reply, next); + reply->complete = 0; + } else { + reply = g_malloc0(sizeof(*reply)); + qemu_cond_init(&reply->cv); + } + reply->msg = msg; + reply->fds = fds; + reply->id = msg->id; + reply->rsize = rsize ? rsize : msg->size; + QTAILQ_INSERT_TAIL(&proxy->pending, reply, next); + + vfio_user_send_locked(proxy, msg, fds); + if (!(msg->flags & VFIO_USER_ERROR)) { + if (!(flags & NOWAIT)) { + while (reply->complete == 0) { + if (!qemu_cond_timedwait(&reply->cv, &proxy->lock, wait_time)) { + msg->flags |= VFIO_USER_ERROR; + msg->error_reply = ETIMEDOUT; + break; + } + } + QTAILQ_INSERT_HEAD(&proxy->free, reply, next); + } else { + reply->nowait = 1; + proxy->last_nowait = reply; + } + } else { + QTAILQ_INSERT_HEAD(&proxy->free, reply, next); + } + + if (iolock) { + qemu_mutex_lock_iothread(); + } +} + +static void vfio_user_request_msg(VFIOUserHdr *hdr, uint16_t cmd, + uint32_t size, uint32_t flags) +{ + static uint16_t next_id; + + hdr->id = qatomic_fetch_inc(&next_id); + hdr->command = cmd; + hdr->size = size; + hdr->flags = (flags & ~VFIO_USER_TYPE) | VFIO_USER_REQUEST; + hdr->error_reply = 0; +} + static QLIST_HEAD(, VFIOProxy) vfio_user_sockets = QLIST_HEAD_INITIALIZER(vfio_user_sockets); @@ -447,3 +534,183 @@ void vfio_user_disconnect(VFIOProxy *proxy) g_free(proxy); } + +struct cap_entry { + const char *name; + int (*check)(QObject *qobj, Error **errp); +}; + +static int caps_parse(QDict *qdict, struct cap_entry caps[], Error **errp) +{ + QObject *qobj; + struct cap_entry *p; + + for (p = caps; p->name != NULL; p++) { + qobj = qdict_get(qdict, p->name); + if (qobj != NULL) { + if (p->check(qobj, errp)) { + return -1; + } + qdict_del(qdict, p->name); + } + } + + /* warning, for now */ + if (qdict_size(qdict) != 0) { + error_printf("spurious capabilities\n"); + } + return 0; +} + +static int check_pgsize(QObject *qobj, Error **errp) +{ + QNum *qn = qobject_to(QNum, qobj); + uint64_t pgsize; + + if (qn == NULL || !qnum_get_try_uint(qn, &pgsize)) { + error_setg(errp, "malformed %s", VFIO_USER_CAP_PGSIZE); + return -1; + } + return pgsize == 4096 ? 0 : -1; +} + +static struct cap_entry caps_migr[] = { + { VFIO_USER_CAP_PGSIZE, check_pgsize }, + { NULL } +}; + +static int check_max_fds(QObject *qobj, Error **errp) +{ + QNum *qn = qobject_to(QNum, qobj); + + if (qn == NULL || !qnum_get_try_uint(qn, &max_send_fds) || + max_send_fds > VFIO_USER_MAX_MAX_FDS) { + error_setg(errp, "malformed %s", VFIO_USER_CAP_MAX_FDS); + return -1; + } + return 0; +} + +static int check_max_xfer(QObject *qobj, Error **errp) +{ + QNum *qn = qobject_to(QNum, qobj); + + if (qn == NULL || !qnum_get_try_uint(qn, &max_xfer_size) || + max_xfer_size > VFIO_USER_MAX_MAX_XFER) { + error_setg(errp, "malformed %s", VFIO_USER_CAP_MAX_XFER); + return -1; + } + return 0; +} + +static int check_migr(QObject *qobj, Error **errp) +{ + QDict *qdict = qobject_to(QDict, qobj); + + if (qdict == NULL || caps_parse(qdict, caps_migr, errp)) { + error_setg(errp, "malformed %s", VFIO_USER_CAP_MAX_FDS); + return -1; + } + return 0; +} + +static struct cap_entry caps_cap[] = { + { VFIO_USER_CAP_MAX_FDS, check_max_fds }, + { VFIO_USER_CAP_MAX_XFER, check_max_xfer }, + { VFIO_USER_CAP_MIGR, check_migr }, + { NULL } +}; + +static int check_cap(QObject *qobj, Error **errp) +{ + QDict *qdict = qobject_to(QDict, qobj); + + if (qdict == NULL || caps_parse(qdict, caps_cap, errp)) { + error_setg(errp, "malformed %s", VFIO_USER_CAP); + return -1; + } + return 0; +} + +static struct cap_entry ver_0_0[] = { + { VFIO_USER_CAP, check_cap }, + { NULL } +}; + +static int caps_check(int minor, const char *caps, Error **errp) +{ + QObject *qobj; + QDict *qdict; + int ret; + + qobj = qobject_from_json(caps, NULL); + if (qobj == NULL) { + error_setg(errp, "malformed capabilities %s", caps); + return -1; + } + qdict = qobject_to(QDict, qobj); + if (qdict == NULL) { + error_setg(errp, "capabilities %s not an object", caps); + qobject_unref(qobj); + return -1; + } + ret = caps_parse(qdict, ver_0_0, errp); + + qobject_unref(qobj); + return ret; +} + +static GString *caps_json(void) +{ + QDict *dict = qdict_new(); + QDict *capdict = qdict_new(); + QDict *migdict = qdict_new(); + GString *str; + + qdict_put_int(migdict, VFIO_USER_CAP_PGSIZE, 4096); + qdict_put_obj(capdict, VFIO_USER_CAP_MIGR, QOBJECT(migdict)); + + qdict_put_int(capdict, VFIO_USER_CAP_MAX_FDS, VFIO_USER_MAX_MAX_FDS); + qdict_put_int(capdict, VFIO_USER_CAP_MAX_XFER, VFIO_USER_DEF_MAX_XFER); + + qdict_put_obj(dict, VFIO_USER_CAP, QOBJECT(capdict)); + + str = qobject_to_json(QOBJECT(dict)); + qobject_unref(dict); + return str; +} + +int vfio_user_validate_version(VFIODevice *vbasedev, Error **errp) +{ + g_autofree VFIOUserVersion *msgp; + GString *caps; + int size, caplen; + + caps = caps_json(); + caplen = caps->len + 1; + size = sizeof(*msgp) + caplen; + msgp = g_malloc0(size); + + vfio_user_request_msg(&msgp->hdr, VFIO_USER_VERSION, size, 0); + msgp->major = VFIO_USER_MAJOR_VER; + msgp->minor = VFIO_USER_MINOR_VER; + memcpy(&msgp->capabilities, caps->str, caplen); + g_string_free(caps, true); + + vfio_user_send_recv(vbasedev->proxy, &msgp->hdr, NULL, 0, 0); + if (msgp->hdr.flags & VFIO_USER_ERROR) { + error_setg_errno(errp, msgp->hdr.error_reply, "version reply"); + return -1; + } + + if (msgp->major != VFIO_USER_MAJOR_VER || + msgp->minor > VFIO_USER_MINOR_VER) { + error_setg(errp, "incompatible server version"); + return -1; + } + if (caps_check(msgp->minor, (char *)msgp + sizeof(*msgp), errp) != 0) { + return -1; + } + + return 0; +}