* [PATCH 0/2] nvmet: fix file backend short I/O and LBA range checks
@ 2026-11-11 14:41 shao.mingyin
2026-11-11 14:42 ` [PATCH 1/2] nvmet: handle short reads and writes in the file backend shao.mingyin
2026-11-11 14:42 ` [PATCH 2/2] nvmet: use overflow-safe range checks " shao.mingyin
0 siblings, 2 replies; 5+ messages in thread
From: shao.mingyin @ 2026-11-11 14:41 UTC (permalink / raw)
To: kbusch, axboe, hch, sagi, linux-nvme; +Cc: linux-kernel, stable
The file backend computes byte offsets from the LBA and block size with
unchecked 64-bit arithmetic and does not handle short reads or writes.
A host can therefore make the backend report success for a transfer that
only covered part of the range, and a large LBA or length can wrap the
range check and reach vfs_fallocate() with an out-of-range offset.
Patch 1 makes nvmet_file_execute_io() stop at an incomplete submit
instead of advancing the position and eventually reporting success, and
converts a zero-byte completion on the common path to -EIO. Patch 2
adds a shared overflow-safe range check and uses it for read, write,
discard and write zeroes.
The two patches are independent and can be applied in any order. Both
carry Fixes: and Cc: stable.
Testing: both were tested on 7.3.0-next-20261030 with the loop transport
and a file-backed namespace under QEMU/TCG. Patch 1 used failslab to
reach the chunked path and a shortened backing file to force an
incomplete chunk; the unpatched target returned 0 with the tail of the
host buffer unchanged and only part of the write reaching the file, and
all four cases return 0x6006 after the patch. Patch 2 wrote SLBA 2^52+1
with a 4096-byte block size, which wraps to offset 0x1000; the unpatched
target accepted the command, and it now fails with 0x6081 (Capacity
Exceeded). Both patches also apply to next-20261105 and pass
checkpatch.pl --strict with no errors.
Shao Mingyin (2):
nvmet: handle short reads and writes in the file backend
nvmet: use overflow-safe range checks in the file backend
drivers/nvme/target/io-cmd-file.c | 48 ++++++++++++++++++++++++++++-----------
1 file changed, 35 insertions(+), 13 deletions(-)
---
base-commit: ddfe51b0a0f5dc9e415cc2c8ad8db17532dd426b
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 1/2] nvmet: handle short reads and writes in the file backend
2026-11-11 14:41 [PATCH 0/2] nvmet: fix file backend short I/O and LBA range checks shao.mingyin
@ 2026-11-11 14:42 ` shao.mingyin
2026-11-11 16:41 ` Christoph Hellwig
2026-11-11 14:42 ` [PATCH 2/2] nvmet: use overflow-safe range checks " shao.mingyin
1 sibling, 1 reply; 5+ messages in thread
From: shao.mingyin @ 2026-11-11 14:42 UTC (permalink / raw)
To: kbusch, axboe, hch, sagi, linux-nvme; +Cc: linux-kernel, stable
From: Shao Mingyin <shao.mingyin@zte.com.cn>
When the file backend falls back to its bvec mempool, large requests
are submitted in synchronous chunks. A short completion advances the
file position by the full chunk length and is eventually overwritten
with the total request length. The target reports success for an
incomplete write or returns unread buffer contents as valid data.
The common completion path has another short-I/O case: a zero-byte
return, such as a read at EOF after the backing file is truncated, is
passed to errno_to_nvme_status() and becomes success.
Stop at any incomplete chunk and convert nonnegative short completions
to -EIO in both paths. Preserve negative errors and full completions.
Fixes: d5eff33ee6f8 ("nvmet: add simple file backed ns support")
Fixes: c6aa3542e010 ("nvmet: add error log support for file backend")
Cc: stable@vger.kernel.org
Signed-off-by: Shao Mingyin <shao.mingyin@zte.com.cn>
---
Tested on 7.3.0-next-20261030 (QEMU/TCG) with the loop transport and a
file-backed namespace on tmpfs (buffered_io=1).
The chunked path is only taken when the bio_vec allocation falls back
to the mempool, so failslab was used to fail that allocation, limited to
nvmet_file_execute_rw() with the stack-trace filter (times=1,
ignore-gfp-wait=N). The short completion was made deterministic by
shrinking the backing file behind nvmet's back (ns->size is only
revalidated on enable) and by leaving only 4 KiB of space in the tmpfs
for the last 64 KiB chunk of a 128 KiB write. The values below are the
status words returned by NVME_IOCTL_IO_CMD:
unpatched patched
read 512 KiB ret=0 ret=24582 (0x6006)
read 256 KiB ret=0 ret=24582
read 128 KiB ret=0 ret=24582
write 128 KiB ret=0 ret=24582
0x6006 contains NVME_SC_INTERNAL plus the Do Not Retry and More bits;
the More bit records the associated error-log entry.
Without the patch the reads report success although the tail of the
host buffer still holds the 0x5a pattern, and the write reports success
although only 68 KiB of 128 KiB reached the file. Controls: without the
injection the short read already fails with 0x6006 on both kernels
(non-chunked path), and with the injection but a full-length read both
return 0.
drivers/nvme/target/io-cmd-file.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/drivers/nvme/target/io-cmd-file.c b/drivers/nvme/target/io-cmd-file.c
index 695963dd932e..e95ab1f08cff 100644
--- a/drivers/nvme/target/io-cmd-file.c
+++ b/drivers/nvme/target/io-cmd-file.c
@@ -113,7 +113,7 @@ static void nvmet_file_io_done(struct kiocb *iocb, long ret)
}
if (unlikely(ret != req->transfer_len))
- status = errno_to_nvme_status(req, ret);
+ status = errno_to_nvme_status(req, ret < 0 ? ret : -EIO);
nvmet_req_complete(req, status);
}
@@ -150,8 +150,11 @@ static bool nvmet_file_execute_io(struct nvmet_req *req, int ki_flags)
if (unlikely(is_sync) &&
(nr_bvec - 1 == 0 || bv_cnt == NVMET_MAX_MPOOL_BVEC)) {
ret = nvmet_file_submit_bvec(req, pos, bv_cnt, len, 0);
- if (ret < 0)
+ if (ret != len) {
+ if (ret >= 0)
+ ret = -EIO;
goto complete;
+ }
pos += len;
bv_cnt = 0;
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 2/2] nvmet: use overflow-safe range checks in the file backend
2026-11-11 14:41 [PATCH 0/2] nvmet: fix file backend short I/O and LBA range checks shao.mingyin
2026-11-11 14:42 ` [PATCH 1/2] nvmet: handle short reads and writes in the file backend shao.mingyin
@ 2026-11-11 14:42 ` shao.mingyin
2026-11-11 16:43 ` Christoph Hellwig
1 sibling, 1 reply; 5+ messages in thread
From: shao.mingyin @ 2026-11-11 14:42 UTC (permalink / raw)
To: kbusch, axboe, hch, sagi, linux-nvme; +Cc: linux-kernel, stable
From: Shao Mingyin <shao.mingyin@zte.com.cn>
The file backend shifts a host-supplied SLBA to form a byte offset
before checking it against the namespace size. The shift can discard
high bits: with a 4 KiB block size, SLBA 2^52 + 1 becomes offset 4096.
The range check then accepts a command aimed outside the namespace and
the backend accesses data at the wrapped offset.
Reject overflowing LBA-to-byte conversions and check the transfer
length against the remaining namespace size without adding it to the
offset. Apply the checks to reads, writes, discard and write zeroes.
Fixes: 9c891c139894 ("nvmet: check fileio lba range access boundaries")
Cc: stable@vger.kernel.org
Signed-off-by: Shao Mingyin <shao.mingyin@zte.com.cn>
---
Tested on 7.3.0-next-20261030 (QEMU/TCG guest) with the loop transport
and a 4 MiB file-backed namespace.
A Write with SLBA = 2^52 + 1 and a 4 KiB block size yields a byte
offset that wraps to 0x1000 and passes the size check:
unpatched patched
ret=0 (write accepted at 0x1000) ret=24705 (Capacity Exceeded)
drivers/nvme/target/io-cmd-file.c | 41 +++++++++++++++++++++++++++----------
1 file changed, 30 insertions(+), 11 deletions(-)
diff --git a/drivers/nvme/target/io-cmd-file.c b/drivers/nvme/target/io-cmd-file.c
index 695963dd932e..d59dbf61b1b0 100644
--- a/drivers/nvme/target/io-cmd-file.c
+++ b/drivers/nvme/target/io-cmd-file.c
@@ -125,17 +125,22 @@ static bool nvmet_file_execute_io(struct nvmet_req *req, int ki_flags)
size_t len = 0, total_len = 0;
ssize_t ret = 0;
loff_t pos;
+ u64 pos64;
int i;
struct scatterlist *sg;
if (req->f.mpool_alloc && nr_bvec > NVMET_MAX_MPOOL_BVEC)
is_sync = true;
- pos = le64_to_cpu(req->cmd->rw.slba) << req->ns->blksize_shift;
- if (unlikely(pos + req->transfer_len > req->ns->size)) {
+ if (unlikely(check_shl_overflow(le64_to_cpu(req->cmd->rw.slba),
+ req->ns->blksize_shift,
+ &pos64) ||
+ pos64 > req->ns->size ||
+ req->transfer_len > req->ns->size - pos64)) {
ret = -ENOSPC;
goto complete;
}
+ pos = pos64;
memset(&req->f.iocb, 0, sizeof(struct kiocb));
for_each_sg(req->sg, sg, req->sg_cnt, i) {
@@ -269,6 +274,16 @@ static void nvmet_file_execute_flush(struct nvmet_req *req)
queue_work(nvmet_wq, &req->f.work);
}
+static bool nvmet_file_check_lba_range(struct nvmet_req *req, u64 slba,
+ u64 nlb, u64 *offset, u64 *len)
+{
+ if (check_shl_overflow(slba, req->ns->blksize_shift, offset) ||
+ check_shl_overflow(nlb, req->ns->blksize_shift, len))
+ return false;
+
+ return *offset <= req->ns->size && *len <= req->ns->size - *offset;
+}
+
static void nvmet_file_execute_discard(struct nvmet_req *req)
{
int mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE;
@@ -279,19 +294,22 @@ static void nvmet_file_execute_discard(struct nvmet_req *req)
int i;
for (i = 0; i <= le32_to_cpu(req->cmd->dsm.nr); i++) {
+ u64 offset64, len64;
+
status = nvmet_copy_from_sgl(req, i * sizeof(range), &range,
sizeof(range));
if (status)
break;
- offset = le64_to_cpu(range.slba) << req->ns->blksize_shift;
- len = le32_to_cpu(range.nlb);
- len <<= req->ns->blksize_shift;
- if (offset + len > req->ns->size) {
+ if (!nvmet_file_check_lba_range(req, le64_to_cpu(range.slba),
+ le32_to_cpu(range.nlb),
+ &offset64, &len64)) {
req->error_slba = le64_to_cpu(range.slba);
status = errno_to_nvme_status(req, -ENOSPC);
break;
}
+ offset = offset64;
+ len = len64;
ret = vfs_fallocate(req->ns->file, mode, offset, len);
if (ret && ret != -EOPNOTSUPP) {
@@ -336,16 +354,17 @@ static void nvmet_file_write_zeroes_work(struct work_struct *w)
int mode = FALLOC_FL_ZERO_RANGE | FALLOC_FL_KEEP_SIZE;
loff_t offset;
loff_t len;
+ u64 offset64, len64;
int ret;
- offset = le64_to_cpu(write_zeroes->slba) << req->ns->blksize_shift;
- len = (((sector_t)le16_to_cpu(write_zeroes->length) + 1) <<
- req->ns->blksize_shift);
-
- if (unlikely(offset + len > req->ns->size)) {
+ if (!nvmet_file_check_lba_range(req, le64_to_cpu(write_zeroes->slba),
+ (u64)le16_to_cpu(write_zeroes->length) + 1,
+ &offset64, &len64)) {
nvmet_req_complete(req, errno_to_nvme_status(req, -ENOSPC));
return;
}
+ offset = offset64;
+ len = len64;
ret = vfs_fallocate(req->ns->file, mode, offset, len);
nvmet_req_complete(req, ret < 0 ? errno_to_nvme_status(req, ret) : 0);
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH 1/2] nvmet: handle short reads and writes in the file backend
2026-11-11 14:42 ` [PATCH 1/2] nvmet: handle short reads and writes in the file backend shao.mingyin
@ 2026-11-11 16:41 ` Christoph Hellwig
0 siblings, 0 replies; 5+ messages in thread
From: Christoph Hellwig @ 2026-11-11 16:41 UTC (permalink / raw)
To: shao.mingyin; +Cc: kbusch, axboe, hch, sagi, linux-nvme, linux-kernel, stable
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH 2/2] nvmet: use overflow-safe range checks in the file backend
2026-11-11 14:42 ` [PATCH 2/2] nvmet: use overflow-safe range checks " shao.mingyin
@ 2026-11-11 16:43 ` Christoph Hellwig
0 siblings, 0 replies; 5+ messages in thread
From: Christoph Hellwig @ 2026-11-11 16:43 UTC (permalink / raw)
To: shao.mingyin; +Cc: kbusch, axboe, hch, sagi, linux-nvme, linux-kernel, stable
On Wed, Nov 11, 2026 at 10:42:47PM +0800, shao.mingyin@zte.com.cn wrote:
> From: Shao Mingyin <shao.mingyin@zte.com.cn>
>
> The file backend shifts a host-supplied SLBA to form a byte offset
> before checking it against the namespace size. The shift can discard
> high bits: with a 4 KiB block size, SLBA 2^52 + 1 becomes offset 4096.
> The range check then accepts a command aimed outside the namespace and
> the backend accesses data at the wrapped offset.
>
> Reject overflowing LBA-to-byte conversions and check the transfer
> length against the remaining namespace size without adding it to the
> offset. Apply the checks to reads, writes, discard and write zeroes.
>
> Fixes: 9c891c139894 ("nvmet: check fileio lba range access boundaries")
> Cc: stable@vger.kernel.org
> Signed-off-by: Shao Mingyin <shao.mingyin@zte.com.cn>
> ---
>
> Tested on 7.3.0-next-20261030 (QEMU/TCG guest) with the loop transport
> and a 4 MiB file-backed namespace.
>
> A Write with SLBA = 2^52 + 1 and a 4 KiB block size yields a byte
> offset that wraps to 0x1000 and passes the size check:
>
> unpatched patched
> ret=0 (write accepted at 0x1000) ret=24705 (Capacity Exceeded)
> drivers/nvme/target/io-cmd-file.c | 41 +++++++++++++++++++++++++++----------
> 1 file changed, 30 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/nvme/target/io-cmd-file.c b/drivers/nvme/target/io-cmd-file.c
> index 695963dd932e..d59dbf61b1b0 100644
> --- a/drivers/nvme/target/io-cmd-file.c
> +++ b/drivers/nvme/target/io-cmd-file.c
> @@ -125,17 +125,22 @@ static bool nvmet_file_execute_io(struct nvmet_req *req, int ki_flags)
> size_t len = 0, total_len = 0;
> ssize_t ret = 0;
> loff_t pos;
> + u64 pos64;
> int i;
> struct scatterlist *sg;
>
> if (req->f.mpool_alloc && nr_bvec > NVMET_MAX_MPOOL_BVEC)
> is_sync = true;
>
> - pos = le64_to_cpu(req->cmd->rw.slba) << req->ns->blksize_shift;
> - if (unlikely(pos + req->transfer_len > req->ns->size)) {
> + if (unlikely(check_shl_overflow(le64_to_cpu(req->cmd->rw.slba),
> + req->ns->blksize_shift,
> + &pos64) ||
> + pos64 > req->ns->size ||
> + req->transfer_len > req->ns->size - pos64)) {
This condition looks like something that should be factored into a
helper to a bit more readable and so that the pos64 variable can be
hidden inside it.
Otherwise looks good.
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-11 16:43 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-11 14:41 [PATCH 0/2] nvmet: fix file backend short I/O and LBA range checks shao.mingyin
2026-11-11 14:42 ` [PATCH 1/2] nvmet: handle short reads and writes in the file backend shao.mingyin
2026-11-11 16:41 ` Christoph Hellwig
2026-11-11 14:42 ` [PATCH 2/2] nvmet: use overflow-safe range checks " shao.mingyin
2026-11-11 16:43 ` Christoph Hellwig
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®