Linux SCSI subsystem development
 help / color / mirror / Atom feed
* [PATCH 0/2] scsi: bsg: fix io_uring passthrough issues
@ 2026-07-20  3:23 Yang Xiuwei
  2026-07-20  3:23 ` [PATCH 1/2] scsi: bsg: fix TOCTOU in io_uring passthrough command setup Yang Xiuwei
  2026-07-20  3:23 ` [PATCH 2/2] scsi: bsg: map io_uring user buffers with GFP_KERNEL Yang Xiuwei
  0 siblings, 2 replies; 5+ messages in thread
From: Yang Xiuwei @ 2026-07-20  3:23 UTC (permalink / raw)
  To: James.Bottomley, martin.petersen
  Cc: rc, axboe, fujita.tomonori, bvanassche, linux-scsi, io-uring,
	Yang Xiuwei

Hi,

Two fixes for the BSG io_uring passthrough path.

Patch 1 addresses a TOCTOU where the shared SQE can change request_len
between validation and copy_from_user(), overflowing scmd->cmnd. This
is a v3 of Rahul Chandelkar's series, taking over after no follow-up to
v2. Relative to v2, READ_ONCE is limited to fields used for validation
and buffer mapping, as requested in review.

Link: https://lore.kernel.org/r/20260527105931.3950913-1-rc@rexion.ai
Link: https://lore.kernel.org/r/20260527191817.142769-1-rc@rexion.ai

Patch 2 uses GFP_KERNEL for user-buffer mapping. IO_URING_F_NONBLOCK
still sets BLK_MQ_REQ_NOWAIT so request-tag allocation does not block;
mapping itself may sleep and need not use GFP_NOWAIT.

Changes for patch 1 since Rahul's v2:
- READ_ONCE only request/request_len and dout/din xfer fields.
- Pass stable map arguments into scsi_bsg_map_user_buffer().

Please review.

Thanks,
Yang Xiuwei

Rahul Chandelkar (1):
  scsi: bsg: fix TOCTOU in io_uring passthrough command setup

Yang Xiuwei (1):
  scsi: bsg: map io_uring user buffers with GFP_KERNEL

 drivers/scsi/scsi_bsg.c | 47 +++++++++++++++++++++++------------------
 1 file changed, 27 insertions(+), 20 deletions(-)

-- 
2.25.1


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/2] scsi: bsg: fix TOCTOU in io_uring passthrough command setup
  2026-07-20  3:23 [PATCH 0/2] scsi: bsg: fix io_uring passthrough issues Yang Xiuwei
@ 2026-07-20  3:23 ` Yang Xiuwei
  2026-07-20  3:37   ` sashiko-bot
  2026-07-20  3:23 ` [PATCH 2/2] scsi: bsg: map io_uring user buffers with GFP_KERNEL Yang Xiuwei
  1 sibling, 1 reply; 5+ messages in thread
From: Yang Xiuwei @ 2026-07-20  3:23 UTC (permalink / raw)
  To: James.Bottomley, martin.petersen
  Cc: rc, axboe, fujita.tomonori, bvanassche, linux-scsi, io-uring,
	stable, Yang Xiuwei

From: Rahul Chandelkar <rc@rexion.ai>

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: 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>
---
 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;
 
-	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


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 2/2] scsi: bsg: map io_uring user buffers with GFP_KERNEL
  2026-07-20  3:23 [PATCH 0/2] scsi: bsg: fix io_uring passthrough issues Yang Xiuwei
  2026-07-20  3:23 ` [PATCH 1/2] scsi: bsg: fix TOCTOU in io_uring passthrough command setup Yang Xiuwei
@ 2026-07-20  3:23 ` Yang Xiuwei
  2026-07-20  3:39   ` sashiko-bot
  1 sibling, 1 reply; 5+ messages in thread
From: Yang Xiuwei @ 2026-07-20  3:23 UTC (permalink / raw)
  To: James.Bottomley, martin.petersen
  Cc: rc, axboe, fujita.tomonori, bvanassche, linux-scsi, io-uring,
	Yang Xiuwei

IO_URING_F_NONBLOCK only needs BLK_MQ_REQ_NOWAIT for request
allocation. Mapping the user buffer may sleep, so use GFP_KERNEL
instead of GFP_NOWAIT.

Signed-off-by: Yang Xiuwei <yangxiuwei@kylinos.cn>
---
 drivers/scsi/scsi_bsg.c | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)

diff --git a/drivers/scsi/scsi_bsg.c b/drivers/scsi/scsi_bsg.c
index c57ce01379de..f90d19fdbd8e 100644
--- a/drivers/scsi/scsi_bsg.c
+++ b/drivers/scsi/scsi_bsg.c
@@ -76,9 +76,8 @@ 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,
-				    bool is_write, u64 buf_addr,
-				    unsigned long buf_len)
+				    unsigned int issue_flags, bool is_write,
+				    u64 buf_addr, unsigned long buf_len)
 {
 	struct iov_iter iter;
 	int ret;
@@ -89,10 +88,10 @@ static int scsi_bsg_map_user_buffer(struct request *req,
 						&iter, ioucmd, issue_flags);
 		if (ret < 0)
 			return ret;
-		ret = blk_rq_map_user_iov(req->q, req, NULL, &iter, gfp_mask);
+		ret = blk_rq_map_user_iov(req->q, req, NULL, &iter, GFP_KERNEL);
 	} else {
 		ret = blk_rq_map_user(req->q, req, NULL, uptr64(buf_addr),
-				      buf_len, gfp_mask);
+				      buf_len, GFP_KERNEL);
 	}
 
 	return ret;
@@ -107,7 +106,6 @@ static int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *iouc
 	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);
@@ -132,10 +130,8 @@ static int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *iouc
 	if (cmd->dout_iovec_count > 0 || cmd->din_iovec_count > 0)
 		return -EOPNOTSUPP;
 
-	if (issue_flags & IO_URING_F_NONBLOCK) {
+	if (issue_flags & IO_URING_F_NONBLOCK)
 		blk_flags = BLK_MQ_REQ_NOWAIT;
-		gfp_mask = GFP_NOWAIT;
-	}
 
 	req = scsi_alloc_request(q, dout_xfer_len ?
 				 REQ_OP_DRV_OUT : REQ_OP_DRV_IN, blk_flags);
@@ -170,8 +166,7 @@ static int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *iouc
 		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);
+					       is_write, buf_addr, buf_len);
 		if (ret)
 			goto out_free_req;
 		pdu->bio = req->bio;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH 1/2] scsi: bsg: fix TOCTOU in io_uring passthrough command setup
  2026-07-20  3:23 ` [PATCH 1/2] scsi: bsg: fix TOCTOU in io_uring passthrough command setup Yang Xiuwei
@ 2026-07-20  3:37   ` sashiko-bot
  0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-07-20  3:37 UTC (permalink / raw)
  To: Yang Xiuwei; +Cc: linux-scsi

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Kernel memory leak of uninitialized sense buffer bytes to user space.
--

--- Patch [1]: [PATCH 1/2] scsi: bsg: fix TOCTOU in io_uring passthrough command setup ---
commit bb53b6e06613a855cd8556487582a73b36038ec3
Author: Rahul Chandelkar <rc@rexion.ai>

scsi: bsg: fix TOCTOU in io_uring passthrough command setup

This commit fixes a TOCTOU vulnerability in scsi_bsg_uring_cmd() by snapshotting
SQE fields with READ_ONCE() before validation and reuse. The stable map
arguments are then passed into scsi_bsg_map_user_buffer().

Link: https://lore.kernel.org/r/20260527105931.3950913-1-rc@rexion.ai

> diff --git a/drivers/scsi/scsi_bsg.c b/drivers/scsi/scsi_bsg.c
> index e80dec53174ea..c57ce01379de5 100644
> --- a/drivers/scsi/scsi_bsg.c
> +++ b/drivers/scsi/scsi_bsg.c
[ ... ]
> @@ -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;
>  

[Severity: High]
This isn't a bug introduced by this patch, but does overwriting scmd->sense_len
with the maximum response size leak uninitialized kernel memory to user space?

The SCSI midlayer expects sense_len to hold the actual valid length of the
sense data, but it is overwritten here with the user's max_response_len.

If a command fails, the low-level driver might populate a small amount of sense
data (e.g., 18 bytes) without zeroing the rest of the buffer.

The completion handler in drivers/scsi/scsi_bsg.c retrieves this artificially
inflated sense_len to determine how many bytes to copy:

scsi_bsg_uring_task_cb() {
    ...
    if (pdu->response_addr)
        sense_len_wr = min_t(u8, scmd->sense_len,
                             SCSI_SENSE_BUFFERSIZE);
    ...
    if (copy_to_user(uptr64(pdu->response_addr), scmd->sense_buffer,
                     sense_len_wr))
    ...
}

Could this copy uninitialized trailing bytes from the reused sense_buffer to
user space?

> -	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);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260720032338.461681-1-yangxiuwei@kylinos.cn?part=1

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH 2/2] scsi: bsg: map io_uring user buffers with GFP_KERNEL
  2026-07-20  3:23 ` [PATCH 2/2] scsi: bsg: map io_uring user buffers with GFP_KERNEL Yang Xiuwei
@ 2026-07-20  3:39   ` sashiko-bot
  0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-07-20  3:39 UTC (permalink / raw)
  To: Yang Xiuwei; +Cc: linux-scsi

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

New issues:
- [High] Unconditionally using GFP_KERNEL forces the io_uring submitter thread to sleep, breaking non-blocking semantics.

Pre-existing issues:
- [High] Using GFP_KERNEL while holding a block layer request tag introduces a tag exhaustion deadlock during memory reclaim.
--

--- Patch [2]: [PATCH 2/2] scsi: bsg: map io_uring user buffers with GFP_KERNEL ---
commit e89699f2c58014fd3728ce2e8bed2b71fc1c0abf
Author: Yang Xiuwei <yangxiuwei@kylinos.cn>

scsi: bsg: map io_uring user buffers with GFP_KERNEL

IO_URING_F_NONBLOCK only needs BLK_MQ_REQ_NOWAIT for request
allocation. Mapping the user buffer may sleep, so use GFP_KERNEL
instead of GFP_NOWAIT.

> diff --git a/drivers/scsi/scsi_bsg.c b/drivers/scsi/scsi_bsg.c
> index c57ce01379de5..f90d19fdbd8ea 100644
> --- a/drivers/scsi/scsi_bsg.c
> +++ b/drivers/scsi/scsi_bsg.c
> @@ -76,9 +76,8 @@ 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,
> -				    bool is_write, u64 buf_addr,
> -				    unsigned long buf_len)
> +				    unsigned int issue_flags, bool is_write,
> +				    u64 buf_addr, unsigned long buf_len)
>  {
>  	struct iov_iter iter;
>  	int ret;
> @@ -89,10 +88,10 @@ static int scsi_bsg_map_user_buffer(struct request *req,
>  						&iter, ioucmd, issue_flags);
>  		if (ret < 0)
>  			return ret;
> -		ret = blk_rq_map_user_iov(req->q, req, NULL, &iter, gfp_mask);
> +		ret = blk_rq_map_user_iov(req->q, req, NULL, &iter, GFP_KERNEL);

[Severity: High]
This is a pre-existing issue, but does using GFP_KERNEL here while holding a
block request tag risk a tag exhaustion deadlock?

The request tag is allocated in scsi_alloc_request() prior to this mapping
step. If blk_rq_map_user_iov() triggers direct memory reclaim, and reclaim
attempts to write dirty pages to this same block device, it will need a tag.
If all tags are currently held by concurrent tasks stuck in this mapping
phase, won't the writeback block indefinitely?

>  	} else {
>  		ret = blk_rq_map_user(req->q, req, NULL, uptr64(buf_addr),
> -				      buf_len, gfp_mask);
> +				      buf_len, GFP_KERNEL);
>  	}
>  
>  	return ret;

[ ... ]

> @@ -132,10 +130,8 @@ static int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *iouc
>  	if (cmd->dout_iovec_count > 0 || cmd->din_iovec_count > 0)
>  		return -EOPNOTSUPP;
>  
> -	if (issue_flags & IO_URING_F_NONBLOCK) {
> +	if (issue_flags & IO_URING_F_NONBLOCK)
>  		blk_flags = BLK_MQ_REQ_NOWAIT;
> -		gfp_mask = GFP_NOWAIT;
> -	}
>  
>  	req = scsi_alloc_request(q, dout_xfer_len ?
>  				 REQ_OP_DRV_OUT : REQ_OP_DRV_IN, blk_flags);

[ ... ]

> @@ -170,8 +166,7 @@ static int scsi_bsg_uring_cmd(struct request_queue *q, struct io_uring_cmd *iouc
>  		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);
> +					       is_write, buf_addr, buf_len);

[Severity: High]
Since this changes the allocation to GFP_KERNEL unconditionally, won't this
illegally block the io_uring submission context when IO_URING_F_NONBLOCK is
set?

If a non-blocking context like SQPOLL is forced to block for memory
allocation or page faults during scsi_bsg_map_user_buffer(), doesn't it stall
all other rings sharing the thread?

Should this path attempt mapping with GFP_NOWAIT first, and return -EAGAIN on
-ENOMEM so the operation can be safely punted to an async io-wq worker?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260720032338.461681-1-yangxiuwei@kylinos.cn?part=2

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-07-20  3:39 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-20  3:23 [PATCH 0/2] scsi: bsg: fix io_uring passthrough issues Yang Xiuwei
2026-07-20  3:23 ` [PATCH 1/2] scsi: bsg: fix TOCTOU in io_uring passthrough command setup Yang Xiuwei
2026-07-20  3:37   ` sashiko-bot
2026-07-20  3:23 ` [PATCH 2/2] scsi: bsg: map io_uring user buffers with GFP_KERNEL Yang Xiuwei
2026-07-20  3:39   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox