git: 2981c8a6491b - stable/15 - bhyve: Validate VirtIO queue guest addresses

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Thu, 22 Oct 2026 17:32:19 UTC
The branch stable/15 has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=2981c8a6491b360e6cb3e714eb6e7ec81dca8cab

commit 2981c8a6491b360e6cb3e714eb6e7ec81dca8cab
Author:     Hayzam Sherif <hayzam@gmail.com>
AuthorDate: 2026-09-30 14:17:24 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-10-22 15:45:41 +0000

    bhyve: Validate VirtIO queue guest addresses
    
    paddr_guest2host() returns NULL when a range does not fit within guest
    RAM.  An unmappable queue address previously left the queue marked
    allocated with ring pointers computed from the failed mapping.  Check
    the legacy virtqueue ring mapping before updating the queue state, and
    ignore an unmappable PFN write after printing a diagnostic.  This leaves
    an unallocated queue unallocated and preserves an existing queue's PFN,
    ring pointers, flags, and indices.
    
    Also check the indirect descriptor table mapping before dereferencing
    it, returning an error if the table is unmappable, and ignore guest
    queue notifications for unallocated queues.
    
    Reviewed by:    markj
    MFC after:      2 weeks
    Sponsored by:   The FreeBSD Foundation
    Differential Revision:  https://reviews.freebsd.org/D59904
    
    (cherry picked from commit 4485bfaed2145e88911bd3522d6fee5c0f8ceb39)
---
 usr.sbin/bhyve/virtio.c | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/usr.sbin/bhyve/virtio.c b/usr.sbin/bhyve/virtio.c
index 304156bf7416..fa3c5f8e63a7 100644
--- a/usr.sbin/bhyve/virtio.c
+++ b/usr.sbin/bhyve/virtio.c
@@ -182,10 +182,15 @@ vi_vq_init(struct virtio_softc *vs, uint32_t pfn)
 	char *base;
 
 	vq = &vs->vs_queues[vs->vs_curq];
-	vq->vq_pfn = pfn;
 	phys = (uint64_t)pfn << VRING_PFN;
 	size = vring_size_aligned(vq->vq_qsize);
 	base = paddr_guest2host(vs->vs_pi->pi_vmctx, phys, size);
+	if (base == NULL) {
+		EPRINTLN("%s: queue %u has invalid GPA %#jx/%#zx",
+		    vs->vs_vc->vc_name, vq->vq_num, (uintmax_t)phys, size);
+		return;
+	}
+	vq->vq_pfn = pfn;
 
 	/* First page(s) are descriptors... */
 	vq->vq_desc = (struct vring_desc *)base;
@@ -349,6 +354,13 @@ vq_getchain(struct vqueue_info *vq, struct iovec *iov, int niov,
 			}
 			vindir = paddr_guest2host(ctx,
 			    vdir->addr, vdir->len);
+			if (vindir == NULL) {
+				EPRINTLN(
+				    "%s: indirect table GPA %#jx/%#x is "
+				    "unmappable", name, (uintmax_t)vdir->addr,
+				    (u_int)vdir->len);
+				return (-1);
+			}
 			/*
 			 * Indirects start at the 0th, then follow
 			 * their own embedded "next"s until those run
@@ -780,6 +792,8 @@ bad:
 			goto done;
 		}
 		vq = &vs->vs_queues[value];
+		if (!vq_ring_ready(vq))
+			goto done;
 		if (vq->vq_notify)
 			(*vq->vq_notify)(DEV_SOFTC(vs), vq);
 		else if (vc->vc_qnotify)