* [PULL 1/5] hw/ufs: Validate MCQ SQ references before use
2026-05-12 4:42 [PULL 0/5] ufs queue Jeuk Kim
@ 2026-05-12 4:42 ` Jeuk Kim
2026-05-12 4:42 ` [PULL 2/5] hw/ufs: Guard MCQ CQ accesses against missing queues Jeuk Kim
` (4 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Jeuk Kim @ 2026-05-12 4:42 UTC (permalink / raw)
To: qemu-devel
Cc: stefanha, qemu-block, qemu-stable, jeuk20.kim, j-young.choi,
Rayhan Ramdhany Hanaputra
From: Jeuk Kim <jeuk20.kim@samsung.com>
A guest can program an out-of-range SQATTR.CQID value, or ring an
MCQ SQ doorbell before the submission queue exists.
Reject SQ creation when the referenced CQ is invalid, and ignore SQ
doorbells for queues that have not been created. This prevents a
guest-triggerable out-of-bounds read and NULL pointer dereference.
Fixes: 5c079578d2e ("hw/ufs: Add support MCQ of UFSHCI 4.0")
Reported-by: Rayhan Ramdhany Hanaputra <hanaputrarayhan@gmail.com>
Cc: qemu-stable@nongnu.org
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
---
hw/ufs/ufs.c | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index cb74cb56bc..d5fba15e2a 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -517,8 +517,13 @@ static bool ufs_mcq_create_sq(UfsHc *u, uint8_t qid, uint32_t attr)
return false;
}
+ if (cqid >= u->params.mcq_maxq) {
+ trace_ufs_err_mcq_create_sq_invalid_cqid(cqid);
+ return false;
+ }
+
if (!u->cq[cqid]) {
- trace_ufs_err_mcq_create_sq_invalid_cqid(qid);
+ trace_ufs_err_mcq_create_sq_invalid_cqid(cqid);
return false;
}
@@ -775,6 +780,11 @@ static void ufs_mcq_process_db(UfsHc *u, uint8_t qid, uint32_t db)
}
sq = u->sq[qid];
+ if (!sq) {
+ trace_ufs_err_mcq_db_wr_invalid_sqid(qid);
+ return;
+ }
+
if (sq->size * sizeof(UfsSqEntry) <= db) {
trace_ufs_err_mcq_db_wr_invalid_db(qid, db);
return;
@@ -788,7 +798,14 @@ static void ufs_write_mcq_op_reg(UfsHc *u, hwaddr offset, uint32_t data,
unsigned size)
{
int qid = offset / sizeof(UfsMcqOpReg);
- UfsMcqOpReg *opr = &u->mcq_op_reg[qid];
+ UfsMcqOpReg *opr;
+
+ if (qid >= u->params.mcq_maxq) {
+ trace_ufs_err_invalid_register_offset(offset);
+ return;
+ }
+
+ opr = &u->mcq_op_reg[qid];
switch (offset % sizeof(UfsMcqOpReg)) {
case offsetof(UfsMcqOpReg, sq.tp):
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PULL 2/5] hw/ufs: Guard MCQ CQ accesses against missing queues
2026-05-12 4:42 [PULL 0/5] ufs queue Jeuk Kim
2026-05-12 4:42 ` [PULL 1/5] hw/ufs: Validate MCQ SQ references before use Jeuk Kim
@ 2026-05-12 4:42 ` Jeuk Kim
2026-05-12 4:42 ` [PULL 3/5] hw/ufs: Reject zero-depth MCQ queues Jeuk Kim
` (3 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Jeuk Kim @ 2026-05-12 4:42 UTC (permalink / raw)
To: qemu-devel
Cc: stefanha, qemu-block, qemu-stable, jeuk20.kim, j-young.choi,
Rayhan Ramdhany Hanaputra
From: Jeuk Kim <jeuk20.kim@samsung.com>
A guest can ring an MCQ CQ doorbell before the completion queue exists.
The CQ head write path then dereferences a NULL CQ through
ufs_mcq_cq_full().
Ignore CQ head updates for missing CQs, and make ufs_mcq_cq_full()
handle a missing CQ defensively.
Fixes: f78762a3cc8 ("hw/ufs: Fix mcq completion queue wraparound")
Reported-by: Rayhan Ramdhany Hanaputra <hanaputrarayhan@gmail.com>
Cc: qemu-stable@nongnu.org
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
---
hw/ufs/ufs.c | 4 ++++
hw/ufs/ufs.h | 9 ++++++++-
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index d5fba15e2a..1819ba2e8a 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -817,6 +817,10 @@ static void ufs_write_mcq_op_reg(UfsHc *u, hwaddr offset, uint32_t data,
case offsetof(UfsMcqOpReg, cq.hp): {
UfsCq *cq = u->cq[qid];
+ if (!cq) {
+ break;
+ }
+
if (ufs_mcq_cq_full(u, qid) && !QTAILQ_EMPTY(&cq->req_list)) {
/* Enqueueing to CQ was blocked because it was full */
qemu_bh_schedule(cq->bh);
diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h
index 13d964c5ae..9e800cafac 100644
--- a/hw/ufs/ufs.h
+++ b/hw/ufs/ufs.h
@@ -203,7 +203,14 @@ static inline bool ufs_mcq_cq_empty(UfsHc *u, uint32_t qid)
static inline bool ufs_mcq_cq_full(UfsHc *u, uint32_t qid)
{
uint32_t tail = ufs_mcq_cq_tail(u, qid);
- uint16_t cq_size = u->cq[qid]->size;
+ UfsCq *cq = u->cq[qid];
+ uint16_t cq_size;
+
+ if (!cq) {
+ return false;
+ }
+
+ cq_size = cq->size;
tail = (tail + sizeof(UfsCqEntry)) % (sizeof(UfsCqEntry) * cq_size);
return tail == ufs_mcq_cq_head(u, qid);
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PULL 3/5] hw/ufs: Reject zero-depth MCQ queues
2026-05-12 4:42 [PULL 0/5] ufs queue Jeuk Kim
2026-05-12 4:42 ` [PULL 1/5] hw/ufs: Validate MCQ SQ references before use Jeuk Kim
2026-05-12 4:42 ` [PULL 2/5] hw/ufs: Guard MCQ CQ accesses against missing queues Jeuk Kim
@ 2026-05-12 4:42 ` Jeuk Kim
2026-05-12 4:42 ` [PULL 4/5] hw/ufs: Keep MCQ SQs alive while requests are outstanding Jeuk Kim
` (2 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Jeuk Kim @ 2026-05-12 4:42 UTC (permalink / raw)
To: qemu-devel; +Cc: stefanha, qemu-block, qemu-stable, jeuk20.kim, j-young.choi
From: Jeuk Kim <jeuk20.kim@samsung.com>
Reject SQATTR.SIZE and CQATTR.SIZE values that produce zero-entry MCQ
queues. Such queues can later trigger a divide-by-zero while advancing
queue pointers.
Fixes: 5c079578d2e ("hw/ufs: Add support MCQ of UFSHCI 4.0")
Cc: qemu-stable@nongnu.org
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
---
hw/ufs/trace-events | 2 ++
hw/ufs/ufs.c | 18 ++++++++++++++++--
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/hw/ufs/trace-events b/hw/ufs/trace-events
index 531dcfc686..7734b35f08 100644
--- a/hw/ufs/trace-events
+++ b/hw/ufs/trace-events
@@ -40,10 +40,12 @@ ufs_err_mcq_db_wr_invalid_sqid(uint8_t qid) "invalid mcq sqid %"PRIu8""
ufs_err_mcq_db_wr_invalid_db(uint8_t qid, uint32_t db) "invalid mcq doorbell sqid %"PRIu8", db %"PRIu32""
ufs_err_mcq_create_sq_invalid_sqid(uint8_t qid) "invalid mcq sqid %"PRIu8""
ufs_err_mcq_create_sq_invalid_cqid(uint8_t qid) "invalid mcq cqid %"PRIu8""
+ufs_err_mcq_create_sq_invalid_size(uint8_t qid) "invalid mcq sq size for sqid %"PRIu8""
ufs_err_mcq_create_sq_already_exists(uint8_t qid) "mcq sqid %"PRIu8 "already exists"
ufs_err_mcq_delete_sq_invalid_sqid(uint8_t qid) "invalid mcq sqid %"PRIu8""
ufs_err_mcq_delete_sq_not_exists(uint8_t qid) "mcq sqid %"PRIu8 "not exists"
ufs_err_mcq_create_cq_invalid_cqid(uint8_t qid) "invalid mcq cqid %"PRIu8""
+ufs_err_mcq_create_cq_invalid_size(uint8_t qid) "invalid mcq cq size for cqid %"PRIu8""
ufs_err_mcq_create_cq_already_exists(uint8_t qid) "mcq cqid %"PRIu8 "already exists"
ufs_err_mcq_delete_cq_invalid_cqid(uint8_t qid) "invalid mcq cqid %"PRIu8""
ufs_err_mcq_delete_cq_not_exists(uint8_t qid) "mcq cqid %"PRIu8 "not exists"
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index 1819ba2e8a..4ccd7aa64d 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -506,6 +506,8 @@ static bool ufs_mcq_create_sq(UfsHc *u, uint8_t qid, uint32_t attr)
UfsMcqReg *reg = &u->mcq_reg[qid];
UfsSq *sq;
uint8_t cqid = FIELD_EX32(attr, SQATTR, CQID);
+ uint16_t qsize =
+ ((FIELD_EX32(attr, SQATTR, SIZE) + 1) << 2) / sizeof(UfsSqEntry);
if (qid >= u->params.mcq_maxq) {
trace_ufs_err_mcq_create_sq_invalid_sqid(qid);
@@ -527,12 +529,17 @@ static bool ufs_mcq_create_sq(UfsHc *u, uint8_t qid, uint32_t attr)
return false;
}
+ if (!qsize) {
+ trace_ufs_err_mcq_create_sq_invalid_size(qid);
+ return false;
+ }
+
sq = g_malloc0(sizeof(*sq));
sq->u = u;
sq->sqid = qid;
sq->cq = u->cq[cqid];
sq->addr = ((uint64_t)reg->squba << 32) | reg->sqlba;
- sq->size = ((FIELD_EX32(attr, SQATTR, SIZE) + 1) << 2) / sizeof(UfsSqEntry);
+ sq->size = qsize;
sq->bh = qemu_bh_new_guarded(ufs_mcq_process_sq, sq,
&DEVICE(u)->mem_reentrancy_guard);
@@ -576,6 +583,8 @@ static bool ufs_mcq_create_cq(UfsHc *u, uint8_t qid, uint32_t attr)
{
UfsMcqReg *reg = &u->mcq_reg[qid];
UfsCq *cq;
+ uint16_t qsize =
+ ((FIELD_EX32(attr, CQATTR, SIZE) + 1) << 2) / sizeof(UfsCqEntry);
if (qid >= u->params.mcq_maxq) {
trace_ufs_err_mcq_create_cq_invalid_cqid(qid);
@@ -587,11 +596,16 @@ static bool ufs_mcq_create_cq(UfsHc *u, uint8_t qid, uint32_t attr)
return false;
}
+ if (!qsize) {
+ trace_ufs_err_mcq_create_cq_invalid_size(qid);
+ return false;
+ }
+
cq = g_malloc0(sizeof(*cq));
cq->u = u;
cq->cqid = qid;
cq->addr = ((uint64_t)reg->cquba << 32) | reg->cqlba;
- cq->size = ((FIELD_EX32(attr, CQATTR, SIZE) + 1) << 2) / sizeof(UfsCqEntry);
+ cq->size = qsize;
cq->bh = qemu_bh_new_guarded(ufs_mcq_process_cq, cq,
&DEVICE(u)->mem_reentrancy_guard);
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PULL 4/5] hw/ufs: Keep MCQ SQs alive while requests are outstanding
2026-05-12 4:42 [PULL 0/5] ufs queue Jeuk Kim
` (2 preceding siblings ...)
2026-05-12 4:42 ` [PULL 3/5] hw/ufs: Reject zero-depth MCQ queues Jeuk Kim
@ 2026-05-12 4:42 ` Jeuk Kim
2026-05-12 4:42 ` [PULL 5/5] hw/ufs: Zero reserved bytes in REPORT LUNS response header Jeuk Kim
2026-05-14 13:21 ` [PULL 0/5] ufs queue Stefan Hajnoczi
5 siblings, 0 replies; 9+ messages in thread
From: Jeuk Kim @ 2026-05-12 4:42 UTC (permalink / raw)
To: qemu-devel; +Cc: stefanha, qemu-block, qemu-stable, jeuk20.kim, j-young.choi
From: Jeuk Kim <jeuk20.kim@samsung.com>
MCQ requests are allocated with their SQ, but can remain in flight on the
CQ list or in the SCSI layer after leaving the SQ free list.
Reject runtime SQ deletion while any request is still outstanding, and
use separate teardown helpers so device exit can still release MCQ
queues after child devices have been unrealized.
Fixes: 5c079578d2e ("hw/ufs: Add support MCQ of UFSHCI 4.0")
Cc: qemu-stable@nongnu.org
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
---
hw/ufs/trace-events | 1 +
hw/ufs/ufs.c | 49 ++++++++++++++++++++++++++++++++++++++-------
2 files changed, 43 insertions(+), 7 deletions(-)
diff --git a/hw/ufs/trace-events b/hw/ufs/trace-events
index 7734b35f08..6f7ea9c95f 100644
--- a/hw/ufs/trace-events
+++ b/hw/ufs/trace-events
@@ -44,6 +44,7 @@ ufs_err_mcq_create_sq_invalid_size(uint8_t qid) "invalid mcq sq size for sqid %"
ufs_err_mcq_create_sq_already_exists(uint8_t qid) "mcq sqid %"PRIu8 "already exists"
ufs_err_mcq_delete_sq_invalid_sqid(uint8_t qid) "invalid mcq sqid %"PRIu8""
ufs_err_mcq_delete_sq_not_exists(uint8_t qid) "mcq sqid %"PRIu8 "not exists"
+ufs_err_mcq_delete_sq_busy(uint8_t qid) "mcq sqid %"PRIu8" has outstanding requests"
ufs_err_mcq_create_cq_invalid_cqid(uint8_t qid) "invalid mcq cqid %"PRIu8""
ufs_err_mcq_create_cq_invalid_size(uint8_t qid) "invalid mcq cq size for cqid %"PRIu8""
ufs_err_mcq_create_cq_already_exists(uint8_t qid) "mcq cqid %"PRIu8 "already exists"
diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c
index 4ccd7aa64d..6548f0f637 100644
--- a/hw/ufs/ufs.c
+++ b/hw/ufs/ufs.c
@@ -556,6 +556,31 @@ static bool ufs_mcq_create_sq(UfsHc *u, uint8_t qid, uint32_t attr)
return true;
}
+static bool ufs_mcq_sq_has_outstanding_req(UfsSq *sq)
+{
+ UfsRequest *req;
+ uint16_t free_reqs = 0;
+
+ QTAILQ_FOREACH(req, &sq->req_list, entry)
+ {
+ free_reqs++;
+ }
+
+ return free_reqs != sq->size;
+}
+
+static void ufs_mcq_free_sq(UfsSq *sq)
+{
+ qemu_bh_delete(sq->bh);
+
+ for (int i = 0; i < sq->size; i++) {
+ ufs_clear_req(&sq->req[i]);
+ }
+
+ g_free(sq->req);
+ g_free(sq);
+}
+
static bool ufs_mcq_delete_sq(UfsHc *u, uint8_t qid)
{
UfsSq *sq;
@@ -572,9 +597,12 @@ static bool ufs_mcq_delete_sq(UfsHc *u, uint8_t qid)
sq = u->sq[qid];
- qemu_bh_delete(sq->bh);
- g_free(sq->req);
- g_free(sq);
+ if (ufs_mcq_sq_has_outstanding_req(sq)) {
+ trace_ufs_err_mcq_delete_sq_busy(qid);
+ return false;
+ }
+
+ ufs_mcq_free_sq(sq);
u->sq[qid] = NULL;
return true;
}
@@ -617,6 +645,12 @@ static bool ufs_mcq_create_cq(UfsHc *u, uint8_t qid, uint32_t attr)
return true;
}
+static void ufs_mcq_free_cq(UfsCq *cq)
+{
+ qemu_bh_delete(cq->bh);
+ g_free(cq);
+}
+
static bool ufs_mcq_delete_cq(UfsHc *u, uint8_t qid)
{
UfsCq *cq;
@@ -640,8 +674,7 @@ static bool ufs_mcq_delete_cq(UfsHc *u, uint8_t qid)
cq = u->cq[qid];
- qemu_bh_delete(cq->bh);
- g_free(cq);
+ ufs_mcq_free_cq(cq);
u->cq[qid] = NULL;
return true;
}
@@ -1884,12 +1917,14 @@ static void ufs_exit(PCIDevice *pci_dev)
for (int i = 0; i < ARRAY_SIZE(u->sq); i++) {
if (u->sq[i]) {
- ufs_mcq_delete_sq(u, i);
+ ufs_mcq_free_sq(u->sq[i]);
+ u->sq[i] = NULL;
}
}
for (int i = 0; i < ARRAY_SIZE(u->cq); i++) {
if (u->cq[i]) {
- ufs_mcq_delete_cq(u, i);
+ ufs_mcq_free_cq(u->cq[i]);
+ u->cq[i] = NULL;
}
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PULL 5/5] hw/ufs: Zero reserved bytes in REPORT LUNS response header
2026-05-12 4:42 [PULL 0/5] ufs queue Jeuk Kim
` (3 preceding siblings ...)
2026-05-12 4:42 ` [PULL 4/5] hw/ufs: Keep MCQ SQs alive while requests are outstanding Jeuk Kim
@ 2026-05-12 4:42 ` Jeuk Kim
2026-05-14 13:21 ` [PULL 0/5] ufs queue Stefan Hajnoczi
5 siblings, 0 replies; 9+ messages in thread
From: Jeuk Kim @ 2026-05-12 4:42 UTC (permalink / raw)
To: qemu-devel; +Cc: stefanha, qemu-block, qemu-stable, jeuk20.kim, j-young.choi
From: Jeuk Kim <jeuk20.kim@samsung.com>
ufs_emulate_report_luns() writes the 4-byte LUN list length into
outbuf[0..3] via stl_be_p() but leaves outbuf[4..7], the reserved
field, uninitialized. Those bytes are then DMA'd to guest memory,
leaking uninitialized QEMU stack data.
Fixes: 7708e298180 ("hw/ufs/lu: skip automatic zero-init of large array")
Cc: qemu-stable@nongnu.org
Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>
---
hw/ufs/lu.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/hw/ufs/lu.c b/hw/ufs/lu.c
index 3f3c9589ce..709d6adcf6 100644
--- a/hw/ufs/lu.c
+++ b/hw/ufs/lu.c
@@ -101,6 +101,10 @@ static int ufs_emulate_report_luns(UfsRequest *req, uint8_t *outbuf,
return SCSI_COMMAND_FAIL;
}
+ if (outbuf_len < 8) {
+ return SCSI_COMMAND_FAIL;
+ }
+ memset(outbuf, 0, 8);
len += 8;
for (uint8_t lun = 0; lun < UFS_MAX_LUS; ++lun) {
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PULL 0/5] ufs queue
2026-05-12 4:42 [PULL 0/5] ufs queue Jeuk Kim
` (4 preceding siblings ...)
2026-05-12 4:42 ` [PULL 5/5] hw/ufs: Zero reserved bytes in REPORT LUNS response header Jeuk Kim
@ 2026-05-14 13:21 ` Stefan Hajnoczi
5 siblings, 0 replies; 9+ messages in thread
From: Stefan Hajnoczi @ 2026-05-14 13:21 UTC (permalink / raw)
To: Jeuk Kim
Cc: qemu-devel, stefanha, qemu-block, qemu-stable, jeuk20.kim,
j-young.choi
[-- Attachment #1: Type: text/plain, Size: 116 bytes --]
Applied, thanks.
Please update the changelog at https://wiki.qemu.org/ChangeLog/11.1 for any user-visible changes.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 9+ messages in thread