scsi: sd: Fix special_vec mempool leak when scsi_alloc_sgtables() fails

sd_set_special_bvec() allocates a special payload page for UNMAP and
WRITE SAME commands. If scsi_alloc_sgtables() fails afterward in
sd_setup_unmap_cmnd() or sd_setup_write_same{10,16}_cmnd(), the SCSI
midlayer does not call uninit_command() because RQF_DONTPREP is not set
yet, leaking the page.

Call sd_uninit_command() on error, and clear RQF_SPECIAL_PAYLOAD after
freeing the page.

Fixes: 81d926e8b5 ("sd: split sd_setup_discard_cmnd")
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Signed-off-by: Yang Xiuwei <yangxiuwei@kylinos.cn>
Reviewed-by: John Garry <john.g.garry@oracle.com>
Link: https://patch.msgid.link/20260707030333.22245-3-yangxiuwei@kylinos.cn
Signed-off-by: Martin K. Petersen (Oracle) <mkp@kernel.org>
This commit is contained in:
Yang Xiuwei 2026-07-07 11:03:32 +08:00 committed by Martin K. Petersen (Oracle)
parent e3cc6ea1a7
commit bb31844d88

View File

@ -958,6 +958,21 @@ static unsigned char sd_setup_protect_cmnd(struct scsi_cmnd *scmd,
return protect;
}
static void sd_uninit_command(struct scsi_cmnd *cmd)
{
struct request *rq = scsi_cmd_to_rq(cmd);
struct scsi_device *sdp = cmd->device;
if (!(rq->rq_flags & RQF_SPECIAL_PAYLOAD))
return;
if (sdp->sector_size > PAGE_SIZE)
mempool_free(rq->special_vec.bv_page, sd_large_page_pool);
else
mempool_free(rq->special_vec.bv_page, sd_page_pool);
rq->rq_flags &= ~RQF_SPECIAL_PAYLOAD;
}
static void *sd_set_special_bvec(struct scsi_cmnd *cmd, unsigned int data_len)
{
struct page *page;
@ -990,6 +1005,7 @@ static blk_status_t sd_setup_unmap_cmnd(struct scsi_cmnd *cmd)
u32 nr_blocks = sectors_to_logical(sdp, blk_rq_sectors(rq));
unsigned int data_len = 24;
char *buf;
blk_status_t ret;
buf = sd_set_special_bvec(cmd, data_len);
if (!buf)
@ -1008,7 +1024,10 @@ static blk_status_t sd_setup_unmap_cmnd(struct scsi_cmnd *cmd)
cmd->transfersize = data_len;
rq->timeout = SD_TIMEOUT;
return scsi_alloc_sgtables(cmd);
ret = scsi_alloc_sgtables(cmd);
if (ret != BLK_STS_OK)
sd_uninit_command(cmd);
return ret;
}
static void sd_config_atomic(struct scsi_disk *sdkp, struct queue_limits *lim)
@ -1079,6 +1098,7 @@ static blk_status_t sd_setup_write_same16_cmnd(struct scsi_cmnd *cmd,
u64 lba = sectors_to_logical(sdp, blk_rq_pos(rq));
u32 nr_blocks = sectors_to_logical(sdp, blk_rq_sectors(rq));
u32 data_len = sdp->sector_size;
blk_status_t ret;
if (!sd_set_special_bvec(cmd, data_len))
return BLK_STS_RESOURCE;
@ -1094,7 +1114,10 @@ static blk_status_t sd_setup_write_same16_cmnd(struct scsi_cmnd *cmd,
cmd->transfersize = data_len;
rq->timeout = unmap ? SD_TIMEOUT : SD_WRITE_SAME_TIMEOUT;
return scsi_alloc_sgtables(cmd);
ret = scsi_alloc_sgtables(cmd);
if (ret != BLK_STS_OK)
sd_uninit_command(cmd);
return ret;
}
static blk_status_t sd_setup_write_same10_cmnd(struct scsi_cmnd *cmd,
@ -1106,6 +1129,7 @@ static blk_status_t sd_setup_write_same10_cmnd(struct scsi_cmnd *cmd,
u64 lba = sectors_to_logical(sdp, blk_rq_pos(rq));
u32 nr_blocks = sectors_to_logical(sdp, blk_rq_sectors(rq));
u32 data_len = sdp->sector_size;
blk_status_t ret;
if (!sd_set_special_bvec(cmd, data_len))
return BLK_STS_RESOURCE;
@ -1121,7 +1145,10 @@ static blk_status_t sd_setup_write_same10_cmnd(struct scsi_cmnd *cmd,
cmd->transfersize = data_len;
rq->timeout = unmap ? SD_TIMEOUT : SD_WRITE_SAME_TIMEOUT;
return scsi_alloc_sgtables(cmd);
ret = scsi_alloc_sgtables(cmd);
if (ret != BLK_STS_OK)
sd_uninit_command(cmd);
return ret;
}
static blk_status_t sd_setup_write_zeroes_cmnd(struct scsi_cmnd *cmd)
@ -1550,20 +1577,6 @@ static blk_status_t sd_init_command(struct scsi_cmnd *cmd)
}
}
static void sd_uninit_command(struct scsi_cmnd *SCpnt)
{
struct request *rq = scsi_cmd_to_rq(SCpnt);
struct scsi_device *sdp = SCpnt->device;
unsigned sector_size = sdp->sector_size;
if (rq->rq_flags & RQF_SPECIAL_PAYLOAD) {
if (sector_size > PAGE_SIZE)
mempool_free(rq->special_vec.bv_page, sd_large_page_pool);
else
mempool_free(rq->special_vec.bv_page, sd_page_pool);
}
}
static bool sd_need_revalidate(struct gendisk *disk, struct scsi_disk *sdkp)
{
if (sdkp->device->removable || sdkp->write_prot) {