Skip to content

Commit c9344bd

Browse files
committed
femu: refuse SGL descriptors with a subtype or a ragged segment
The low nibble of an SGL descriptor's type byte is its subtype, and over PCIe only the address subtype exists; an offset descriptor was used as an absolute address. A segment length that is not a multiple of 16 was truncated to whole descriptors rather than refused. The new sgl qtest writes through a data block and a last segment, then checks each malformed form is refused; each check fails without its fix.
1 parent bb63df5 commit c9344bd

2 files changed

Lines changed: 86 additions & 2 deletions

File tree

hw/femu/dma.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ uint16_t nvme_map_prp(QEMUSGList *qsg, QEMUIOVector *iov, uint64_t prp1,
155155
* Map an NVMe SGL into a QEMUSGList, the PRP-path equivalent of nvme_map_prp.
156156
* Supports address SGLs: DATA_BLOCK descriptors (a direct segment) and
157157
* SEGMENT / LAST_SEGMENT descriptors (which point at a further array of
158-
* descriptors in guest memory). Bit-bucket and keyed SGLs are rejected.
158+
* descriptors in guest memory). Bit-bucket and keyed SGLs are rejected, and
159+
* so is any subtype but address: the offset one exists only for fabrics.
159160
* CMB-resident SGLs are not special-cased (rare); the descriptors are read
160161
* from guest memory via nvme_addr_read. Builds the same qsg the backend_rw
161162
* path consumes, so no other code path changes.
@@ -174,6 +175,9 @@ uint16_t nvme_map_sgl(QEMUSGList *qsg, QEMUIOVector *iov,
174175
while (len) {
175176
uint8_t type = NVME_SGL_TYPE(sgl.type);
176177

178+
if (NVME_SGL_SUBTYPE(sgl.type)) {
179+
goto inval;
180+
}
177181
if (type == NVME_SGL_DESCR_TYPE_DATA_BLOCK) {
178182
uint32_t dlen = le32_to_cpu(sgl.len);
179183

@@ -200,7 +204,8 @@ uint16_t nvme_map_sgl(QEMUSGList *qsg, QEMUIOVector *iov,
200204
int i;
201205
bool chained = false;
202206

203-
if (!ndesc || ndesc > max_descrs) {
207+
if (!ndesc || ndesc > max_descrs ||
208+
seg_bytes % sizeof(NvmeSglDescriptor)) {
204209
goto inval;
205210
}
206211
descs = g_malloc(seg_bytes);
@@ -209,6 +214,10 @@ uint16_t nvme_map_sgl(QEMUSGList *qsg, QEMUIOVector *iov,
209214
uint8_t dt = NVME_SGL_TYPE(descs[i].type);
210215
uint32_t dl = le32_to_cpu(descs[i].len);
211216

217+
if (NVME_SGL_SUBTYPE(descs[i].type)) {
218+
g_free(descs);
219+
goto inval;
220+
}
212221
/* only the final entry of a non-last segment may chain */
213222
if (dt == NVME_SGL_DESCR_TYPE_DATA_BLOCK) {
214223
if (!dl || dl > len) {

hw/femu/tests/qtest/femu-test.c

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2076,6 +2076,78 @@ static void femu_test_wide_lba(void *obj, void *data, QGuestAllocator *alloc)
20762076
femu_disable(&c);
20772077
}
20782078

2079+
/*
2080+
* An SGL descriptor has its type in the high nibble and a subtype in the low
2081+
* one, and over PCIe only the address subtype exists; a segment is a whole
2082+
* number of 16-byte descriptors. Lists breaking either rule were mapped.
2083+
*/
2084+
static uint16_t femu_sgl_write(FemuCtrlState *c, const NvmeSglDescriptor *sgl)
2085+
{
2086+
NvmeRwCmd rw;
2087+
uint16_t want = c->cid;
2088+
uint16_t got;
2089+
uint16_t status;
2090+
2091+
memset(&rw, 0, sizeof(rw));
2092+
rw.opcode = NVME_CMD_WRITE;
2093+
rw.flags = 1 << 6; /* PSDT: SGL */
2094+
rw.nsid = cpu_to_le32(1);
2095+
memcpy(&rw.dptr.sgl, sgl, sizeof(*sgl));
2096+
rw.nlb = cpu_to_le16(FEMU_DATA_SIZE / c->lba_size - 1);
2097+
2098+
femu_submit(c, &c->io, (NvmeCmd *)&rw);
2099+
status = femu_complete(c, &c->io, &got, NULL);
2100+
g_assert_cmpint(got, ==, want);
2101+
return FEMU_SC(status);
2102+
}
2103+
2104+
static void femu_test_sgl(void *obj, void *data, QGuestAllocator *alloc)
2105+
{
2106+
QFemu *femu = obj;
2107+
QTestState *qts = femu->dev.bus->qts;
2108+
FemuCtrlState c = { 0 };
2109+
NvmeSglDescriptor blk = { 0 };
2110+
NvmeSglDescriptor seg = { 0 };
2111+
uint64_t buf, list;
2112+
2113+
femu_enable(&c, &femu->dev, alloc);
2114+
femu_create_io_queues(&c);
2115+
2116+
buf = guest_alloc(alloc, FEMU_DATA_SIZE);
2117+
list = guest_alloc(alloc, 4096);
2118+
qtest_memset(qts, buf, 0x5a, FEMU_DATA_SIZE);
2119+
2120+
/* one data block, then the same block through a last segment */
2121+
blk.addr = cpu_to_le64(buf);
2122+
blk.len = cpu_to_le32(FEMU_DATA_SIZE);
2123+
g_assert_cmpint(femu_sgl_write(&c, &blk), ==, NVME_SUCCESS);
2124+
qtest_memwrite(qts, list, &blk, sizeof(blk));
2125+
seg.addr = cpu_to_le64(list);
2126+
seg.len = cpu_to_le32(sizeof(blk));
2127+
seg.type = NVME_SGL_DESCR_TYPE_LAST_SEGMENT << 4;
2128+
g_assert_cmpint(femu_sgl_write(&c, &seg), ==, NVME_SUCCESS);
2129+
2130+
/* a segment length that is not a whole number of descriptors */
2131+
seg.len = cpu_to_le32(sizeof(blk) + 4);
2132+
g_assert_cmpint(femu_sgl_write(&c, &seg), ==, NVME_INVALID_FIELD);
2133+
seg.len = cpu_to_le32(sizeof(blk));
2134+
2135+
/* the offset subtype, which only fabrics define, directly and listed */
2136+
blk.type = 0x1;
2137+
g_assert_cmpint(femu_sgl_write(&c, &blk), ==, NVME_INVALID_FIELD);
2138+
qtest_memwrite(qts, list, &blk, sizeof(blk));
2139+
g_assert_cmpint(femu_sgl_write(&c, &seg), ==, NVME_INVALID_FIELD);
2140+
seg.type |= 0x1;
2141+
blk.type = 0;
2142+
qtest_memwrite(qts, list, &blk, sizeof(blk));
2143+
g_assert_cmpint(femu_sgl_write(&c, &seg), ==, NVME_INVALID_FIELD);
2144+
2145+
guest_free(alloc, list);
2146+
guest_free(alloc, buf);
2147+
femu_queue_free(&c, &c.io);
2148+
femu_disable(&c);
2149+
}
2150+
20792151
static void femu_register_nodes(void)
20802152
{
20812153
QOSGraphEdgeOptions opts = {
@@ -2195,6 +2267,9 @@ static void femu_register_nodes(void)
21952267
"femu_mode=1,secsz=512,secs_per_pg=8,pgs_per_blk=16,"
21962268
"blks_per_pl=80,pls_per_lun=1,luns_per_ch=4,nchs=4"
21972269
});
2270+
qos_add_test("sgl", "femu", femu_test_sgl, &(QOSGraphTestOptions) {
2271+
.edge.extra_device_opts = "sgl=on"
2272+
});
21982273
qos_add_test("wide-lba-4k", "femu", femu_test_wide_lba,
21992274
&(QOSGraphTestOptions) {
22002275
.edge.extra_device_opts =

0 commit comments

Comments
 (0)