[PATCH v3] cxl/mbox: Bound the Get Supported Logs entry count by the payload
From: Gaobin Huang
Date: Tue Sep 29 2026 - 02:10:26 EST
The Get Supported Logs response includes a device supplied entry count.
cxl_enumerate_cmds() uses that count without validating it against the
returned payload length. The command is only issued with .min_out = 2,
so a device may report more entries than it delivered and the driver
reads past the end of the buffer. This is probe time, so it happens on
every boot of a machine with such a device, without any host action.
CXL r4.0 Table 8-249 defines the count as the number of entries returned in
this payload, not a running total, so a count the payload cannot hold is a
malformed response rather than a partial list.
Validate the count in cxl_get_gsl(), where the payload length is known, so
no caller can use an entry without a count it can trust, as
get_supported_features() already does for the feature count. Raise
.min_out to the response header so a response too short to hold the
count is rejected by cxl_internal_send_cmd().
Reproduced with QEMU modified to return an inconsistent entry count. KASAN
reported an out-of-bounds read in cxl_enumerate_cmds().
Reported-by: sashiko-bot@xxxxxxxxxx
Link: https://lore.kernel.org/all/20260917105302.165CF1F000FF@xxxxxxxxxxxxxxx/
Suggested-by: Richard Cheng <icheng@xxxxxxxxxx>
Signed-off-by: Gaobin Huang <huanggaobin23@xxxxxxxxxx>
---
v2 -> v3.
- validate the entry count inside cxl_get_gsl(), which owns the buffer and
can return the error itself, instead of returning the length to the
caller; cxl_enumerate_cmds() is unchanged (Richard Cheng).
- raise .min_out to struct_size(ret, entry, 0) so cxl_internal_send_cmd()
rejects a response too short for the header, which removes the manual
length check and the underflow it guarded against (Richard Cheng,
sashiko).
- the early return that leaked the GSL buffer on that path is gone with it
(sashiko).
- cut the commit message down, drop the claim that no existing hardware is
affected, and capitalize the subject (Alison Schofield); the count is
malformed rather than partial (CXL r4.0 Table 8-249, Richard Cheng).
drivers/cxl/core/mbox.c | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/drivers/cxl/core/mbox.c b/drivers/cxl/core/mbox.c
index 55828a836..6fd074dbb 100644
--- a/drivers/cxl/core/mbox.c
+++ b/drivers/cxl/core/mbox.c
@@ -799,6 +799,7 @@ static struct cxl_mbox_get_supported_logs *cxl_get_gsl(struct cxl_memdev_state *
struct cxl_mailbox *cxl_mbox = &mds->cxlds.cxl_mbox;
struct cxl_mbox_get_supported_logs *ret;
struct cxl_mbox_cmd mbox_cmd;
+ size_t max_entries;
int rc;
ret = kvmalloc(cxl_mbox->payload_size, GFP_KERNEL);
@@ -809,8 +810,8 @@ static struct cxl_mbox_get_supported_logs *cxl_get_gsl(struct cxl_memdev_state *
.opcode = CXL_MBOX_OP_GET_SUPPORTED_LOGS,
.size_out = cxl_mbox->payload_size,
.payload_out = ret,
- /* At least the record number field must be valid */
- .min_out = 2,
+ /* At least the header must be valid */
+ .min_out = struct_size(ret, entry, 0),
};
rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd);
if (rc < 0) {
@@ -818,6 +819,15 @@ static struct cxl_mbox_get_supported_logs *cxl_get_gsl(struct cxl_memdev_state *
return ERR_PTR(rc);
}
+ max_entries = (mbox_cmd.size_out - struct_size(ret, entry, 0)) /
+ sizeof(ret->entry[0]);
+ if (le16_to_cpu(ret->entries) > max_entries) {
+ dev_err(mds->cxlds.dev,
+ "GSL: device claimed %u entries but the payload holds %zu\n",
+ le16_to_cpu(ret->entries), max_entries);
+ kvfree(ret);
+ return ERR_PTR(-EIO);
+ }
return ret;
}
base-commit: 999811aca000b0d3d1c838c60dc9db7c72eb0c73
--
2.34.1