scsi: bsg: Fix TOCTOU in io_uring passthrough command setup

scsi_bsg_uring_cmd() reads bsg_uring_cmd from the shared mmap'd SQE.
Userspace can change a field after we check it and before we use it.
request_len is the sharp case: it can grow past sizeof(scmd->cmnd) after
the bound check and overflow scmd->cmnd in copy_from_user().

READ_ONCE() the SQE fields we check or use into locals before use.

Fixes: 7b6d3255e7 ("scsi: bsg: add io_uring passthrough handler")
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/r/20260527105931.3950913-1-rc@rexion.ai
Signed-off-by: Rahul Chandelkar <rc@rexion.ai>
Co-developed-by: Yang Xiuwei <yangxiuwei@kylinos.cn>
Signed-off-by: Yang Xiuwei <yangxiuwei@kylinos.cn>
Link: https://patch.msgid.link/20260817080730.967879-3-yangxiuwei@kylinos.cn
Signed-off-by: Martin K. Petersen (Oracle) <mkp@kernel.org>
This commit is contained in:
Rahul Chandelkar 2026-08-17 16:07:29 +08:00 committed by Martin K. Petersen (Oracle)
parent ece06de726
commit 4b3c5965fc

View File

@ -77,12 +77,10 @@ static enum rq_end_io_ret scsi_bsg_uring_cmd_done(struct request *req,
static int scsi_bsg_map_user_buffer(struct request *req,
struct io_uring_cmd *ioucmd,
unsigned int issue_flags, gfp_t gfp_mask)
unsigned int issue_flags, gfp_t gfp_mask,
bool is_write, u64 buf_addr,
unsigned long buf_len)
{
const struct bsg_uring_cmd *cmd = io_uring_sqe128_cmd(ioucmd->sqe, struct bsg_uring_cmd);
bool is_write = cmd->dout_xfer_len > 0;
u64 buf_addr = is_write ? cmd->dout_xferp : cmd->din_xferp;
unsigned long buf_len = is_write ? cmd->dout_xfer_len : cmd->din_xfer_len;
struct iov_iter iter;
int ret;
@ -105,21 +103,28 @@ static int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *iouc
unsigned int issue_flags, bool open_for_write)
{
struct scsi_bsg_uring_cmd_pdu *pdu = scsi_bsg_uring_cmd_pdu(ioucmd);
const struct bsg_uring_cmd *cmd = io_uring_sqe128_cmd(ioucmd->sqe, struct bsg_uring_cmd);
const struct bsg_uring_cmd *cmd =
io_uring_sqe128_cmd(ioucmd->sqe, struct bsg_uring_cmd);
struct scsi_cmnd *scmd;
struct request *req;
blk_mq_req_flags_t blk_flags = 0;
gfp_t gfp_mask = GFP_KERNEL;
u64 request = READ_ONCE(cmd->request);
u32 request_len = READ_ONCE(cmd->request_len);
u64 dout_xferp = READ_ONCE(cmd->dout_xferp);
u32 dout_xfer_len = READ_ONCE(cmd->dout_xfer_len);
u64 din_xferp = READ_ONCE(cmd->din_xferp);
u32 din_xfer_len = READ_ONCE(cmd->din_xfer_len);
int ret;
if (cmd->protocol != BSG_PROTOCOL_SCSI ||
cmd->subprotocol != BSG_SUB_PROTOCOL_SCSI_CMD)
return -EINVAL;
if (!cmd->request || cmd->request_len == 0)
if (!request || request_len == 0)
return -EINVAL;
if (cmd->dout_xfer_len && cmd->din_xfer_len) {
if (dout_xfer_len && din_xfer_len) {
pr_warn_once("BIDI support in bsg has been removed.\n");
return -EOPNOTSUPP;
}
@ -132,20 +137,20 @@ static int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *iouc
gfp_mask = GFP_NOWAIT;
}
req = scsi_alloc_request(q, cmd->dout_xfer_len ?
req = scsi_alloc_request(q, dout_xfer_len ?
REQ_OP_DRV_OUT : REQ_OP_DRV_IN, blk_flags);
if (IS_ERR(req))
return PTR_ERR(req);
scmd = blk_mq_rq_to_pdu(req);
if (cmd->request_len > sizeof(scmd->cmnd)) {
if (request_len > sizeof(scmd->cmnd)) {
ret = -EINVAL;
goto out_free_req;
}
scmd->cmd_len = cmd->request_len;
scmd->cmd_len = request_len;
scmd->allowed = SG_DEFAULT_RETRIES;
if (copy_from_user(scmd->cmnd, uptr64(cmd->request), cmd->request_len)) {
if (copy_from_user(scmd->cmnd, uptr64(request), request_len)) {
ret = -EFAULT;
goto out_free_req;
}
@ -158,8 +163,14 @@ static int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *iouc
pdu->response_addr = cmd->response;
pdu->max_response_len = cmd->max_response_len;
if (cmd->dout_xfer_len || cmd->din_xfer_len) {
ret = scsi_bsg_map_user_buffer(req, ioucmd, issue_flags, gfp_mask);
if (dout_xfer_len || din_xfer_len) {
bool is_write = dout_xfer_len > 0;
u64 buf_addr = is_write ? dout_xferp : din_xferp;
unsigned long buf_len = is_write ? dout_xfer_len : din_xfer_len;
ret = scsi_bsg_map_user_buffer(req, ioucmd, issue_flags,
gfp_mask, is_write, buf_addr,
buf_len);
if (ret)
goto out_free_req;
pdu->bio = req->bio;