From: Denis V. Lunev <[email protected]> UNMAP checks each descriptor against the capacity only when its turn comes, so a parameter list with an out-of-range descriptor in the middle discards everything before it and then fails with LOGICAL BLOCK ADDRESS OUT OF RANGE. Validate the whole list before the first discard, so the command either fails without touching the medium or runs to the end.
Signed-off-by: Denis V. Lunev <[email protected]> Cc: Paolo Bonzini <[email protected]> Cc: Kevin Wolf <[email protected]> Cc: Hanna Reitz <[email protected]> Cc: [email protected] --- hw/scsi/scsi-disk.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c index 6341089ddf..73c1ecba0b 100644 --- a/hw/scsi/scsi-disk.c +++ b/hw/scsi/scsi-disk.c @@ -1773,13 +1773,6 @@ static void scsi_unmap_complete_noio(UnmapCBData *data, int ret) r->sector = sector_num * (s->qdev.blocksize / BDRV_SECTOR_SIZE); r->sector_count = nb_sectors * (s->qdev.blocksize / BDRV_SECTOR_SIZE); - if (!check_lba_range(s, sector_num, nb_sectors)) { - block_acct_invalid(blk_get_stats(s->qdev.conf.blk), - BLOCK_ACCT_UNMAP); - scsi_check_condition(r, SENSE_CODE(LBA_OUT_OF_RANGE)); - goto done; - } - block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct, r->sector_count * BDRV_SECTOR_SIZE, BLOCK_ACCT_UNMAP); @@ -1794,8 +1787,6 @@ static void scsi_unmap_complete_noio(UnmapCBData *data, int ret) } scsi_req_complete(&r->req, GOOD); - -done: scsi_req_unref(&r->req); g_free(data); } @@ -1824,6 +1815,8 @@ static void scsi_disk_emulate_unmap(SCSIDiskReq *r, uint8_t *inbuf) uint8_t *p = inbuf; int len = r->req.cmd.xfer; UnmapCBData *data; + int count; + int i; /* Reject ANCHOR=1. */ if (r->req.cmd.buf[1] & 0x1) { @@ -1849,10 +1842,21 @@ static void scsi_disk_emulate_unmap(SCSIDiskReq *r, uint8_t *inbuf) return; } + count = lduw_be_p(&p[2]) >> 4; + for (i = 0; i < count; i++) { + if (!check_lba_range(s, ldq_be_p(&p[8 + i * 16]), + ldl_be_p(&p[8 + i * 16 + 8]))) { + block_acct_invalid(blk_get_stats(s->qdev.conf.blk), + BLOCK_ACCT_UNMAP); + scsi_check_condition(r, SENSE_CODE(LBA_OUT_OF_RANGE)); + return; + } + } + data = g_new0(UnmapCBData, 1); data->r = r; data->inbuf = &p[8]; - data->count = lduw_be_p(&p[2]) >> 4; + data->count = count; /* The matching unref is in scsi_unmap_complete, before data is freed. */ scsi_req_ref(&r->req); -- 2.53.0
