Commit d30254aec9 for qemu.org
commit d30254aec93141d6b89262986a62764b2ae07177
Author: Paolo Bonzini <pbonzini@redhat.com>
Date: Wed Aug 26 19:22:41 2026 +0200
scsi-disk: fix out-of-bound read in WRITE SAME
A guest with an attached scsi-hd can force QEMU's SCSI disk emulation
to read roughly 60 KiB past the end of a heap buffer, copying that out
of bounds host memory into the guest's own disk image.
WRITE SAME computes the request transfer length at dev->blocksize
when the request is prepared and sets cmd->xfer from dev->blocksize.
scsi_disk_emulate_command() then uses cmd->xfer as the size of the
request buffer.
However, MODE SELECT can race with the WRITE SAME command and guest raise
the logical block size to any value whose low bits fit 0xfe00, up to 65024.
In the presence of this race, scsi_disk_emulate_write_same() will read
from memory as many bytes as indicated by the *new* dev->blocksize,
and write it to disk.
The read length in WRITE SAME must be bounded by the buffer that was
actually allocated, not by the mutable s->qdev.blocksize, so clamp the
length used against inbuf to r->buflen. Re-validating req->cmd.xfer
against the current block size would not work because the race is
intrinsic in the SCSI protocol.
I am not sure if this is exploitable with virtio-scsi and other SG-capable
HBAs, because it should process the WRITE SAME input immediately,
without letting the MODE SELECT command race with it.
Fixes: 356c4c441ec ("scsi-disk: allow MODE SELECT block descriptor to set the block size", 2022-07-13)
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4365
Cc: qemu-stable@nongnu.org
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c
index 1b0cce128c..5bb7a974d6 100644
--- a/hw/scsi/scsi-disk.c
+++ b/hw/scsi/scsi-disk.c
@@ -1911,6 +1911,7 @@ static void scsi_disk_emulate_write_same(SCSIDiskReq *r, uint8_t *inbuf)
SCSIRequest *req = &r->req;
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
uint32_t nb_sectors = scsi_data_cdb_xfer(r->req.cmd.buf);
+ uint32_t buflen = MIN(s->qdev.blocksize, r->buflen);
WriteSameCBData *data;
uint8_t *buf;
int i, l;
@@ -1930,7 +1931,7 @@ static void scsi_disk_emulate_write_same(SCSIDiskReq *r, uint8_t *inbuf)
return;
}
- if ((req->cmd.buf[1] & 0x1) || buffer_is_zero(inbuf, s->qdev.blocksize)) {
+ if ((req->cmd.buf[1] & 0x1) || buffer_is_zero(inbuf, buflen)) {
int flags = (req->cmd.buf[1] & 0x8) ? BDRV_REQ_MAY_UNMAP : 0;
/* The request is used as the AIO opaque value, so add a ref. */
@@ -1956,7 +1957,7 @@ static void scsi_disk_emulate_write_same(SCSIDiskReq *r, uint8_t *inbuf)
qemu_iovec_init_external(&data->qiov, &data->iov, 1);
for (i = 0; i < data->iov.iov_len; i += l) {
- l = MIN(s->qdev.blocksize, data->iov.iov_len - i);
+ l = MIN(buflen, data->iov.iov_len - i);
memcpy(&buf[i], inbuf, l);
}