bhyve nvme: Fix out-of-bound IOV array access

Summary:
NVMe operations indicate the memory region(s) associated with a command
via physical region pages (PRPs). Since each PRP has a fixed size,
contiguous memory regions larger than the PRP size require multiple PRP
entries.

Instead of issuing a blockif call for each PRP, the NVMe emulation
concatenates multiple contiguous PRP entries into a single blockif
request. The test for contiguous regions has a bug such that it
mistakenly treats an initial PRP address of zero as a contiguous range
and concatenates it with the previous. But because there is no previous
IOV, the concatenation code corrupts the IO request structure and leads
to a segmentation fault when the blockif request completes.

Fix is to test for the existence of a previous range before trying to
concatenate the current range with the previous one.

While in the area, rename pci_nvme_append_iov_req()'s lba parameter to
offset to match its usage.

PR:             264177
Reported by:    Robert Morris <rtm@lcs.mit.edu>
Reviewed by:	jhb
MFC after:      2 weeks
Differential Revision:	https://reviews.freebsd.org/D35328
This commit is contained in:
Chuck Tuffli 2022-06-09 11:19:32 -07:00
parent 14e3d3248a
commit 88951aaaee

View File

@ -2168,9 +2168,10 @@ pci_nvme_out_of_range(struct pci_nvme_blockstore *nvstore, uint64_t slba,
static int
pci_nvme_append_iov_req(struct pci_nvme_softc *sc, struct pci_nvme_ioreq *req,
uint64_t gpaddr, size_t size, int do_write, uint64_t lba)
uint64_t gpaddr, size_t size, int do_write, uint64_t offset)
{
int iovidx;
bool range_is_contiguous;
if (req == NULL)
return (-1);
@ -2179,8 +2180,17 @@ pci_nvme_append_iov_req(struct pci_nvme_softc *sc, struct pci_nvme_ioreq *req,
return (-1);
}
/* concatenate contig block-iovs to minimize number of iovs */
if ((req->prev_gpaddr + req->prev_size) == gpaddr) {
/*
* Minimize the number of IOVs by concatenating contiguous address
* ranges. If the IOV count is zero, there is no previous range to
* concatenate.
*/
if (req->io_req.br_iovcnt == 0)
range_is_contiguous = false;
else
range_is_contiguous = (req->prev_gpaddr + req->prev_size) == gpaddr;
if (range_is_contiguous) {
iovidx = req->io_req.br_iovcnt - 1;
req->io_req.br_iov[iovidx].iov_base =
@ -2194,7 +2204,7 @@ pci_nvme_append_iov_req(struct pci_nvme_softc *sc, struct pci_nvme_ioreq *req,
} else {
iovidx = req->io_req.br_iovcnt;
if (iovidx == 0) {
req->io_req.br_offset = lba;
req->io_req.br_offset = offset;
req->io_req.br_resid = 0;
req->io_req.br_param = req;
}