Re: [PATCH 1/2] scsi: bsg: fix TOCTOU in io_uring passthrough command setup
Caleb Sander Mateos <[email protected]> Thu, 23 Jul 2026 09:56:08 -0700
| Newsgroups | org.kernel.vger.io-uring,org.kernel.vger.linux-scsi,org.kernel.vger.stable |
|---|---|
| Message-ID | <CADUfDZpAW261MhFJg434=4nZFMD7mV=LL7F-ZiAWPa7dYvGpcQ@mail.gmail.com> |
On Sun, Jul 19, 2026 at 8:27 PM Yang Xiuwei <[email protected]> wrote: > > From: Rahul Chandelkar <[email protected]> > > scsi_bsg_uring_cmd() reads bsg_uring_cmd fields from the shared mmap'd > SQE via io_uring_sqe128_cmd(). On the inline path the SQE is still > user-writable, so request_len can change between the bounds check and > copy_from_user(), overflowing scmd->cmnd. > > Snapshot request/request_len and the transfer fields used for buffer > mapping with READ_ONCE before validation and reuse. Pass the stable > map arguments into scsi_bsg_map_user_buffer() so it does not re-read > the SQE. > > Fixes: 7b6d3255e7f8 ("scsi: bsg: add io_uring passthrough handler") > Cc: [email protected] > Link: https://lore.kernel.org/r/[email protected] > Signed-off-by: Rahul Chandelkar <[email protected]> > Co-developed-by: Yang Xiuwei <[email protected]> > Signed-off-by: Yang Xiuwei <[email protected]> > --- > drivers/scsi/scsi_bsg.c | 40 ++++++++++++++++++++++++++-------------- > 1 file changed, 26 insertions(+), 14 deletions(-) > > diff --git a/drivers/scsi/scsi_bsg.c b/drivers/scsi/scsi_bsg.c > index e80dec53174e..c57ce01379de 100644 > --- a/drivers/scsi/scsi_bsg.c > +++ b/drivers/scsi/scsi_bsg.c > @@ -76,12 +76,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; > > @@ -104,21 +102,29 @@ 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; > + /* Snapshot SQE fields used for validation and buffer mapping. */ > + 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; > } > @@ -131,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 +164,14 @@ static int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *iouc > scmd->sense_len = cmd->max_response_len ? > min(cmd->max_response_len, SCSI_SENSE_BUFFERSIZE) : SCSI_SENSE_BUFFERSIZE; cmd->max_response_len is passed through here, shouldn't it be accessed using READ_ONCE() too? Omitting READ_ONCE() only seems safe for values that are ignored (except for a possible check that they match a fixed value). Ditto for cmd->timeout_ms later in this function. Best, Caleb > > - 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; > -- > 2.25.1 > >