* [PATCH 1/8] scsi: target: take COMPARE AND WRITE data from the write half
2026-10-06 9:33 [PATCH 0/8] scsi: target: keep command bytes inside the sg Jia Jia
@ 2026-10-06 9:33 ` Jia Jia
2026-10-06 9:33 ` [PATCH 2/8] scsi: target: keep REPORT REFERRALS stores inside the buffer Jia Jia
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Jia Jia @ 2026-10-06 9:33 UTC (permalink / raw)
To: Martin K . Petersen
Cc: Jan Engelhardt, Hannes Reinecke, Paolo Bonzini, Akinobu Mita,
James Bottomley, linux-scsi, target-devel, linux-kernel, Jia Jia
compare_and_write_callback() builds the write sgl as
sg->offset + block_size on the first data sg. That offset is the end
of the compare buffer, not the write buffer.
vhost-scsi keeps each descriptor inside one page. A 512-byte compare
buffer placed at the end of a page makes offset + block_size equal to
PAGE_SIZE, and the write then uses the next physical page. The kernel
subsequently reads 512 bytes from that page. If the adjacent memory
region happens to be a freed slab object, KASAN reports:
BUG: KASAN: slab-use-after-free in copy_folio_from_iter_atomic
Read of size 512
compare_and_write_callback
__target_execute_cmd
sbc_execute_rw
fd_execute_rw
fd_do_rw
vfs_iter_write
copy_folio_from_iter_atomic
Skip the compare bytes and describe only the sg entries that hold the
write half. A write that crosses an sg boundary stays in those entries
instead of being forced into one slot.
Free that table with sg_free_table(). sg_alloc_table() chains once
the write half has more entries than fit in one page, and kfree() of
cmd->t_data_sg would drop only the first allocation.
Fixes: d94e5a61357a ("target: fix COMPARE_AND_WRITE non zero SGL offset data corruption")
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
drivers/target/target_core_sbc.c | 100 +++++++++++++++++--------
drivers/target/target_core_transport.c | 11 ++-
2 files changed, 80 insertions(+), 31 deletions(-)
diff --git a/drivers/target/target_core_sbc.c b/drivers/target/target_core_sbc.c
index 21f5cb86d70c..adef903652ac 100644
--- a/drivers/target/target_core_sbc.c
+++ b/drivers/target/target_core_sbc.c
@@ -433,19 +433,74 @@ compare_and_write_do_cmp(struct scatterlist *read_sgl, unsigned int read_nents,
return ret;
}
+/*
+ * Data-out is compare bytes followed by write bytes. Take the write
+ * half from whatever sg entries hold it. Do not add block_size onto
+ * the first sg offset: that page may end before the write half.
+ */
+static int sbc_caw_build_write_sg(struct sg_table *tbl,
+ struct scatterlist *data_sg,
+ unsigned int data_nents, unsigned int skip,
+ unsigned int len)
+{
+ struct scatterlist *sg, *out;
+ unsigned int nents = 0, left = len, left_skip = skip;
+ unsigned int avail, chunk;
+ int i;
+
+ for_each_sg(data_sg, sg, data_nents, i) {
+ if (!sg->length)
+ continue;
+ if (!left)
+ break;
+ if (left_skip >= sg->length) {
+ left_skip -= sg->length;
+ continue;
+ }
+ avail = sg->length - left_skip;
+ chunk = min(left, avail);
+ nents++;
+ left -= chunk;
+ left_skip = 0;
+ }
+ if (!nents || left)
+ return -EINVAL;
+
+ if (sg_alloc_table(tbl, nents, GFP_KERNEL))
+ return -ENOMEM;
+
+ left = len;
+ left_skip = skip;
+ out = tbl->sgl;
+ for_each_sg(data_sg, sg, data_nents, i) {
+ if (!sg->length)
+ continue;
+ if (!left)
+ break;
+ if (left_skip >= sg->length) {
+ left_skip -= sg->length;
+ continue;
+ }
+ avail = sg->length - left_skip;
+ chunk = min(left, avail);
+ sg_set_page(out, sg_page(sg), chunk, sg->offset + left_skip);
+ left -= chunk;
+ left_skip = 0;
+ out = sg_next(out);
+ }
+ return 0;
+}
+
static sense_reason_t compare_and_write_callback(struct se_cmd *cmd, bool success,
int *post_ret)
{
struct se_device *dev = cmd->se_dev;
struct sg_table write_tbl = { };
struct scatterlist *write_sg;
- struct sg_mapping_iter m;
- unsigned int len;
- unsigned int block_size = dev->dev_attrib.block_size;
- unsigned int compare_len = (cmd->t_task_nolb * block_size);
+ unsigned int compare_len = (cmd->t_task_nolb * dev->dev_attrib.block_size);
unsigned int miscmp_off = 0;
sense_reason_t ret = TCM_NO_SENSE;
- int i;
+ int rc;
if (!success) {
/*
@@ -499,34 +554,19 @@ static sense_reason_t compare_and_write_callback(struct se_cmd *cmd, bool succes
} else if (ret)
goto out;
- if (sg_alloc_table(&write_tbl, cmd->t_data_nents, GFP_KERNEL) < 0) {
- pr_err("Unable to allocate compare_and_write sg\n");
- ret = TCM_OUT_OF_RESOURCES;
+ rc = sbc_caw_build_write_sg(&write_tbl, cmd->t_data_sg,
+ cmd->t_data_nents, compare_len,
+ compare_len);
+ if (rc) {
+ pr_err("Unable to build compare_and_write sg\n");
+ if (rc == -ENOMEM)
+ ret = TCM_OUT_OF_RESOURCES;
+ else
+ ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
goto out;
}
write_sg = write_tbl.sgl;
- i = 0;
- len = compare_len;
- sg_miter_start(&m, cmd->t_data_sg, cmd->t_data_nents, SG_MITER_TO_SG);
- /*
- * Currently assumes NoLB=1 and SGLs are PAGE_SIZE..
- */
- while (len) {
- sg_miter_next(&m);
-
- if (block_size < PAGE_SIZE) {
- sg_set_page(&write_sg[i], m.page, block_size,
- m.piter.sg->offset + block_size);
- } else {
- sg_miter_next(&m);
- sg_set_page(&write_sg[i], m.page, block_size,
- m.piter.sg->offset);
- }
- len -= block_size;
- i++;
- }
- sg_miter_stop(&m);
/*
* Save the original SGL + nents values before updating to new
* assignments, to be released in transport_free_pages() ->
@@ -535,7 +575,7 @@ static sense_reason_t compare_and_write_callback(struct se_cmd *cmd, bool succes
cmd->t_data_sg_orig = cmd->t_data_sg;
cmd->t_data_sg = write_sg;
cmd->t_data_nents_orig = cmd->t_data_nents;
- cmd->t_data_nents = 1;
+ cmd->t_data_nents = write_tbl.nents;
cmd->sam_task_attr = TCM_HEAD_TAG;
cmd->transport_complete_callback = compare_and_write_post;
diff --git a/drivers/target/target_core_transport.c b/drivers/target/target_core_transport.c
index dcfe94594916..3cae5ed67d41 100644
--- a/drivers/target/target_core_transport.c
+++ b/drivers/target/target_core_transport.c
@@ -22,6 +22,7 @@
#include <linux/module.h>
#include <linux/ratelimit.h>
#include <linux/vmalloc.h>
+#include <linux/scatterlist.h>
#include <linux/unaligned.h>
#include <net/sock.h>
#include <net/tcp.h>
@@ -2724,10 +2725,18 @@ static inline void transport_reset_sgl_orig(struct se_cmd *cmd)
* Check for saved t_data_sg that may be used for COMPARE_AND_WRITE
* emulation, and free + reset pointers if necessary..
*/
+ struct sg_table table = { };
+
if (!cmd->t_data_sg_orig)
return;
- kfree(cmd->t_data_sg);
+ /*
+ * compare_and_write_callback() built this with sg_alloc_table().
+ * Above SG_MAX_SINGLE_ALLOC the tail is a separate allocation.
+ */
+ table.sgl = cmd->t_data_sg;
+ table.orig_nents = cmd->t_data_nents;
+ sg_free_table(&table);
cmd->t_data_sg = cmd->t_data_sg_orig;
cmd->t_data_sg_orig = NULL;
cmd->t_data_nents = cmd->t_data_nents_orig;
--
2.34.1
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 2/8] scsi: target: keep REPORT REFERRALS stores inside the buffer
2026-10-06 9:33 [PATCH 0/8] scsi: target: keep command bytes inside the sg Jia Jia
2026-10-06 9:33 ` [PATCH 1/8] scsi: target: take COMPARE AND WRITE data from the write half Jia Jia
@ 2026-10-06 9:33 ` Jia Jia
2026-10-06 9:33 ` [PATCH 3/8] scsi: target: reject a short SET TARGET PORT GROUPS list Jia Jia
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Jia Jia @ 2026-10-06 9:33 UTC (permalink / raw)
To: Martin K . Petersen
Cc: Jan Engelhardt, Hannes Reinecke, Paolo Bonzini, Akinobu Mita,
James Bottomley, linux-scsi, target-devel, linux-kernel, Jia Jia
target_emulate_report_referrals() stores an 8-byte LBA when
data_length > off. That test only shows that one byte is left. An
allocation length of 9 therefore writes buf[8] through buf[15].
vhost-scsi keeps one sg inside a page. Nine bytes placed at page
offset 4087 end on the page boundary, so the extra seven bytes are the
next physical page. That page is not part of the data-in sgl.
The check from commit 38edd7245771 ("target_core_alua: check for buffer
overflow") still walks every map entry, so the returned data length stays
the full descriptor size. Keep the walk.
Encode each LBA locally, then copy only the bytes that fit in the
remaining allocation. This also preserves the valid prefix when the
allocation ends in the middle of an LBA field.
The one-byte descriptor fields already test data_length > off.
KASAN reports:
BUG: KASAN: use-after-free in target_emulate_report_referrals+0x100/0x380 [target_core_mod]
Write of size 8
target_emulate_report_referrals
__target_execute_cmd
target_execute_cmd
transport_generic_new_cmd
__target_submit
target_queued_submit_work
process_one_work
worker_thread
kthread
ret_from_fork
Fixes: 38edd7245771 ("target_core_alua: check for buffer overflow")
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
drivers/target/target_core_alua.c | 23 +++++++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/drivers/target/target_core_alua.c b/drivers/target/target_core_alua.c
--- a/drivers/target/target_core_alua.c
+++ b/drivers/target/target_core_alua.c
@@ -44,9 +44,22 @@ static u32 alua_lu_gps_count;
static u16 alua_lu_gps_counter;
static u32 alua_lu_gps_count;
static DEFINE_SPINLOCK(lu_gps_lock);
static LIST_HEAD(lu_gps_list);
struct t10_alua_lu_gp *default_lu_gp;
+static void
+target_emulate_report_referrals_copy_lba(unsigned char *buf, u32 off,
+ u32 data_length, u64 lba)
+{
+ unsigned char lba_buf[sizeof(lba)];
+
+ if (off >= data_length)
+ return;
+ put_unaligned_be64(lba, lba_buf);
+ memcpy(&buf[off], lba_buf,
+ min_t(u32, sizeof(lba_buf), data_length - off));
+}
+
/*
@@ -85,13 +98,15 @@ target_emulate_report_referrals(struct se_cmd *cmd)
list_for_each_entry(map, &dev->t10_alua.lba_map_list,
lba_map_list) {
int desc_num = off + 3;
int pg_num;
off += 4;
- if (cmd->data_length > off)
- put_unaligned_be64(map->lba_map_first_lba, &buf[off]);
+ target_emulate_report_referrals_copy_lba(buf, off,
+ cmd->data_length,
+ map->lba_map_first_lba);
off += 8;
- if (cmd->data_length > off)
- put_unaligned_be64(map->lba_map_last_lba, &buf[off]);
+ target_emulate_report_referrals_copy_lba(buf, off,
+ cmd->data_length,
+ map->lba_map_last_lba);
off += 8;
rd_len += 20;
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 3/8] scsi: target: reject a short SET TARGET PORT GROUPS list
2026-10-06 9:33 [PATCH 0/8] scsi: target: keep command bytes inside the sg Jia Jia
2026-10-06 9:33 ` [PATCH 1/8] scsi: target: take COMPARE AND WRITE data from the write half Jia Jia
2026-10-06 9:33 ` [PATCH 2/8] scsi: target: keep REPORT REFERRALS stores inside the buffer Jia Jia
@ 2026-10-06 9:33 ` Jia Jia
2026-10-06 9:33 ` [PATCH 4/8] scsi: target: copy a DIF insert tuple across prot sgs Jia Jia
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Jia Jia @ 2026-10-06 9:33 UTC (permalink / raw)
To: Martin K . Petersen
Cc: Jan Engelhardt, Hannes Reinecke, Paolo Bonzini, Akinobu Mita,
James Bottomley, linux-scsi, target-devel, linux-kernel, Jia Jia
target_emulate_set_target_port_groups() accepts any parameter list of
4 bytes or more. The walk then reads a 4-byte descriptor whenever any
byte remains. A 5-byte list reads ptr[0] and get_unaligned_be16(ptr + 2),
which is buf[6] and buf[7].
Explicit ALUA is enabled on a new target port group. Access state 0 is
Active/Optimized, so that read is reached. vhost-scsi keeps one sg
inside a page. Five bytes at page offset 4091 end on the page boundary,
and the port-group id is the next physical page.
A legal list is a 4-byte header plus 4-byte descriptors. Reject a
length that is not a multiple of 4 before any port-group state changes.
KASAN reports:
BUG: KASAN: use-after-free in target_emulate_set_target_port_groups+0x1dc/0x540 [target_core_mod]
Read of size 2
target_emulate_set_target_port_groups
__target_execute_cmd
target_execute_cmd
vhost_scsi_write_pending
transport_generic_new_cmd
__target_submit
target_queued_submit_work
process_one_work
worker_thread
kthread
ret_from_fork
ret_from_fork_asm
Fixes: 0d7f1299ca55 ("target: report too-small parameter lists everywhere")
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
drivers/target/target_core_alua.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/target/target_core_alua.c b/drivers/target/target_core_alua.c
index 140154d93c43..e2439f2e468a 100644
--- a/drivers/target/target_core_alua.c
+++ b/drivers/target/target_core_alua.c
@@ -283,9 +283,9 @@ target_emulate_set_target_port_groups(struct se_cmd *cmd)
int alua_access_state, primary = 0, valid_states;
u16 tg_pt_id, rtpi;
- if (cmd->data_length < 4) {
+ if (cmd->data_length < 4 || (cmd->data_length & 3)) {
- pr_warn("SET TARGET PORT GROUPS parameter list length %u too"
- " small\n", cmd->data_length);
+ pr_warn("SET TARGET PORT GROUPS list length %u too small or not a multiple of 4\n",
+ cmd->data_length);
return TCM_INVALID_PARAMETER_LIST;
}
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 4/8] scsi: target: copy a DIF insert tuple across prot sgs
2026-10-06 9:33 [PATCH 0/8] scsi: target: keep command bytes inside the sg Jia Jia
` (2 preceding siblings ...)
2026-10-06 9:33 ` [PATCH 3/8] scsi: target: reject a short SET TARGET PORT GROUPS list Jia Jia
@ 2026-10-06 9:33 ` Jia Jia
2026-10-06 9:33 ` [PATCH 5/8] scsi: target: copy a DIF verify " Jia Jia
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Jia Jia @ 2026-10-06 9:33 UTC (permalink / raw)
To: Martin K . Petersen
Cc: Jan Engelhardt, Hannes Reinecke, Paolo Bonzini, Akinobu Mita,
James Bottomley, linux-scsi, target-devel, linux-kernel, Jia Jia
sbc_dif_generate() walks each protection sg and stores an 8 byte
t10_pi_tuple while j < sg->length. The store is not limited to the
bytes that remain in that sg.
vhost-scsi maps a protection buffer with iov_iter_get_pages2() and
keeps each sg inside one page. Eight PI bytes that start at page
offset 4092 become two sg entries, 4 bytes at offset 4092 and 4 bytes
at offset 0. The first entry still takes the loop. For DIF Type 1
the ref_tag store is 4 bytes at paddr + 4, which is the next physical
page.
Software INSERT reaches this function when the fabric does not
advertise DIN_INSERT or DOUT_INSERT. vhost-scsi advertises only
DIN_PASS and DOUT_PASS. With fabric_prot_type 1, a READ is
TARGET_PROT_DIN_INSERT and the generated tuple is written into the
guest PI buffer.
Copy the 8 byte tuple across protection sg entries. Four bytes at the
end of one entry and four at the start of the next stay one tuple. A
tail with no following entry is left unwritten. The walk maps each
page with kmap_local_page().
KASAN reports:
BUG: KASAN: use-after-free in sbc_dif_generate+0x20c/0x640 [target_core_mod]
Write of size 4
sbc_dif_generate
target_complete_ok_work
process_one_work
worker_thread
kthread
ret_from_fork
ret_from_fork_asm
Fixes: 66a3d5bc47d2 ("target/sbc: Add sbc_dif_generate software emulation")
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
drivers/target/target_core_sbc.c | 181 ++++++++++++++++++++++---------
1 file changed, 132 insertions(+), 49 deletions(-)
diff --git a/drivers/target/target_core_sbc.c b/drivers/target/target_core_sbc.c
index adef903652ac..12275ebad95c 100644
--- a/drivers/target/target_core_sbc.c
+++ b/drivers/target/target_core_sbc.c
@@ -1250,72 +1250,155 @@ sbc_execute_unmap(struct se_cmd *cmd)
return ret;
}
+/*
+ * Copy one 8 byte PI tuple to or from the prot sg. The data page sits
+ * above the prot map, so a prot sg change drops and restores it. A
+ * short tail releases both maps and returns false.
+ */
+static bool
+sbc_dif_prot_copy(struct scatterlist **psgp, void **paddrp,
+ unsigned int *poffp, struct scatterlist **dsgp,
+ void **daddrp, void *buf, bool to_prot)
+{
+ struct scatterlist *psg = *psgp;
+ struct scatterlist *dsg = *dsgp;
+ void *paddr = *paddrp;
+ void *daddr = *daddrp;
+ unsigned int poff = *poffp;
+ unsigned int len = sizeof(struct t10_pi_tuple);
+ u8 *p = buf;
+
+ while (len) {
+ unsigned int take;
+
+ if (!psg || poff >= psg->length) {
+ if (daddr)
+ kunmap_local(daddr - dsg->offset);
+ if (paddr)
+ kunmap_local(paddr - psg->offset);
+ daddr = NULL;
+ paddr = NULL;
+ psg = psg ? sg_next(psg) : NULL;
+ if (!psg) {
+ *psgp = NULL;
+ *paddrp = NULL;
+ *daddrp = NULL;
+ return false;
+ }
+ poff = 0;
+ if (!psg->length)
+ continue;
+ paddr = kmap_local_page(sg_page(psg)) + psg->offset;
+ daddr = kmap_local_page(sg_page(dsg)) + dsg->offset;
+ }
+
+ take = min_t(unsigned int, len, psg->length - poff);
+ if (to_prot)
+ memcpy(paddr + poff, p, take);
+ else
+ memcpy(p, paddr + poff, take);
+ p += take;
+ poff += take;
+ len -= take;
+ }
+
+ *psgp = psg;
+ *paddrp = paddr;
+ *poffp = poff;
+ *dsgp = dsg;
+ *daddrp = daddr;
+ return true;
+}
+
void
sbc_dif_generate(struct se_cmd *cmd)
{
struct se_device *dev = cmd->se_dev;
- struct t10_pi_tuple *sdt;
- struct scatterlist *dsg = cmd->t_data_sg, *psg;
+ struct scatterlist *dsg = cmd->t_data_sg;
+ struct scatterlist *psg = cmd->t_prot_sg;
sector_t sector = cmd->t_task_lba;
void *daddr, *paddr;
- int i, j, offset = 0;
+ unsigned int poff = 0;
+ int offset = 0;
unsigned int block_size = dev->dev_attrib.block_size;
- for_each_sg(cmd->t_prot_sg, psg, cmd->t_prot_nents, i) {
- paddr = kmap_atomic(sg_page(psg)) + psg->offset;
- daddr = kmap_atomic(sg_page(dsg)) + dsg->offset;
+ if (!psg || !dsg)
+ return;
- for (j = 0; j < psg->length;
- j += sizeof(*sdt)) {
- __u16 crc;
- unsigned int avail;
+ paddr = kmap_local_page(sg_page(psg)) + psg->offset;
+ daddr = kmap_local_page(sg_page(dsg)) + dsg->offset;
- if (offset >= dsg->length) {
- offset -= dsg->length;
- kunmap_atomic(daddr - dsg->offset);
- dsg = sg_next(dsg);
- if (!dsg) {
- kunmap_atomic(paddr - psg->offset);
- return;
- }
- daddr = kmap_atomic(sg_page(dsg)) + dsg->offset;
- }
+ for (;;) {
+ struct t10_pi_tuple sdt;
+ struct scatterlist *mark_psg;
+ unsigned int mark_off;
+ __u16 crc;
+ unsigned int avail;
- sdt = paddr + j;
- avail = min(block_size, dsg->length - offset);
- crc = crc_t10dif(daddr + offset, avail);
- if (avail < block_size) {
- kunmap_atomic(daddr - dsg->offset);
- dsg = sg_next(dsg);
- if (!dsg) {
- kunmap_atomic(paddr - psg->offset);
- return;
- }
- daddr = kmap_atomic(sg_page(dsg)) + dsg->offset;
- offset = block_size - avail;
- crc = crc_t10dif_update(crc, daddr, offset);
- } else {
- offset += block_size;
- }
+ if (poff >= psg->length && !sg_next(psg))
+ break;
- sdt->guard_tag = cpu_to_be16(crc);
- if (cmd->prot_type == TARGET_DIF_TYPE1_PROT)
- sdt->ref_tag = cpu_to_be32(sector & 0xffffffff);
- sdt->app_tag = 0;
+ mark_psg = psg;
+ mark_off = poff;
+ if (!sbc_dif_prot_copy(&psg, &paddr, &poff, &dsg, &daddr,
+ &sdt, false))
+ return;
+
+ if (offset >= dsg->length) {
+ offset -= dsg->length;
+ kunmap_local(daddr - dsg->offset);
+ dsg = sg_next(dsg);
+ if (!dsg) {
+ kunmap_local(paddr - psg->offset);
+ return;
+ }
+ daddr = kmap_local_page(sg_page(dsg)) + dsg->offset;
+ }
- pr_debug("DIF %s INSERT sector: %llu guard_tag: 0x%04x"
- " app_tag: 0x%04x ref_tag: %u\n",
- (cmd->data_direction == DMA_TO_DEVICE) ?
- "WRITE" : "READ", (unsigned long long)sector,
- sdt->guard_tag, sdt->app_tag,
- be32_to_cpu(sdt->ref_tag));
+ avail = min(block_size, dsg->length - offset);
+ crc = crc_t10dif(daddr + offset, avail);
+ if (avail < block_size) {
+ kunmap_local(daddr - dsg->offset);
+ dsg = sg_next(dsg);
+ if (!dsg) {
+ kunmap_local(paddr - psg->offset);
+ return;
+ }
+ daddr = kmap_local_page(sg_page(dsg)) + dsg->offset;
+ offset = block_size - avail;
+ crc = crc_t10dif_update(crc, daddr, offset);
+ } else {
+ offset += block_size;
+ }
- sector++;
+ sdt.guard_tag = cpu_to_be16(crc);
+ if (cmd->prot_type == TARGET_DIF_TYPE1_PROT)
+ sdt.ref_tag = cpu_to_be32(sector & 0xffffffff);
+ sdt.app_tag = 0;
+
+ pr_debug("DIF %s INSERT sector: %llu guard_tag: 0x%04x app_tag: 0x%04x ref_tag: %u\n",
+ (cmd->data_direction == DMA_TO_DEVICE) ?
+ "WRITE" : "READ", (unsigned long long)sector,
+ sdt.guard_tag, sdt.app_tag,
+ be32_to_cpu(sdt.ref_tag));
+
+ if (psg != mark_psg) {
+ kunmap_local(daddr - dsg->offset);
+ kunmap_local(paddr - psg->offset);
+ psg = mark_psg;
+ paddr = kmap_local_page(sg_page(psg)) + psg->offset;
+ daddr = kmap_local_page(sg_page(dsg)) + dsg->offset;
}
+ poff = mark_off;
+ if (!sbc_dif_prot_copy(&psg, &paddr, &poff, &dsg, &daddr,
+ &sdt, true))
+ return;
- kunmap_atomic(daddr - dsg->offset);
- kunmap_atomic(paddr - psg->offset);
+ sector++;
}
+
+ kunmap_local(daddr - dsg->offset);
+ kunmap_local(paddr - psg->offset);
}
static sense_reason_t
--
2.34.1
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 5/8] scsi: target: copy a DIF verify tuple across prot sgs
2026-10-06 9:33 [PATCH 0/8] scsi: target: keep command bytes inside the sg Jia Jia
` (3 preceding siblings ...)
2026-10-06 9:33 ` [PATCH 4/8] scsi: target: copy a DIF insert tuple across prot sgs Jia Jia
@ 2026-10-06 9:33 ` Jia Jia
2026-10-06 9:33 ` [PATCH 6/8] scsi: target: limit DIF block CRC to each data sg Jia Jia
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Jia Jia @ 2026-10-06 9:33 UTC (permalink / raw)
To: Martin K . Petersen
Cc: Jan Engelhardt, Hannes Reinecke, Paolo Bonzini, Akinobu Mita,
James Bottomley, linux-scsi, target-devel, linux-kernel, Jia Jia
sbc_dif_verify() loads an 8 byte t10_pi_tuple while i < psg->length.
A protection sg shorter than 8 bytes still enters the loop.
vhost-scsi keeps each sg inside one page. Eight PI bytes that start
at page offset 4092 are mapped as 4 bytes at that offset and 4 bytes
on the next guest page. The first sg still satisfies i < length, and
the tuple read continues into the next physical page.
Software verify runs when the fabric does not advertise DOUT_STRIP.
vhost-scsi advertises only DIN_PASS and DOUT_PASS. With
fabric_prot_type 1, a WRITE is TARGET_PROT_DOUT_STRIP and
sbc_dif_verify() reads the guest PI buffer.
Copy the 8 byte tuple across protection sg entries before the check.
Four bytes at the end of one entry and four at the start of the next
stay one tuple. A tail with no following entry fails the command.
KASAN reports:
BUG: KASAN: use-after-free in sbc_dif_verify+0x5ab/0x790 [target_core_mod]
Read of size 4
sbc_dif_verify
target_execute_cmd
vhost_scsi_write_pending
transport_generic_new_cmd
__target_submit
target_queued_submit_work
process_one_work
worker_thread
kthread
ret_from_fork
ret_from_fork_asm
Fixes: 18213afbd8ce ("target: handle odd SG mapping for data transfer memory")
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
drivers/target/target_core_sbc.c | 117 ++++++++++++++++---------------
1 file changed, 60 insertions(+), 57 deletions(-)
diff --git a/drivers/target/target_core_sbc.c b/drivers/target/target_core_sbc.c
index 12275ebad95c..c0aeb8886743 100644
--- a/drivers/target/target_core_sbc.c
+++ b/drivers/target/target_core_sbc.c
@@ -1492,81 +1492,84 @@ sbc_dif_verify(struct se_cmd *cmd, sector_t start, unsigned int sectors,
unsigned int ei_lba, struct scatterlist *psg, int psg_off)
{
struct se_device *dev = cmd->se_dev;
- struct t10_pi_tuple *sdt;
struct scatterlist *dsg = cmd->t_data_sg;
sector_t sector = start;
void *daddr, *paddr;
- int i;
sense_reason_t rc;
+ unsigned int poff = psg_off;
int dsg_off = 0;
unsigned int block_size = dev->dev_attrib.block_size;
- for (; psg && sector < start + sectors; psg = sg_next(psg)) {
- paddr = kmap_atomic(sg_page(psg)) + psg->offset;
- daddr = kmap_atomic(sg_page(dsg)) + dsg->offset;
-
- for (i = psg_off; i < psg->length &&
- sector < start + sectors;
- i += sizeof(*sdt)) {
- __u16 crc;
- unsigned int avail;
-
- if (dsg_off >= dsg->length) {
- dsg_off -= dsg->length;
- kunmap_atomic(daddr - dsg->offset);
- dsg = sg_next(dsg);
- if (!dsg) {
- kunmap_atomic(paddr - psg->offset);
- return 0;
- }
- daddr = kmap_atomic(sg_page(dsg)) + dsg->offset;
- }
+ if (!psg || !dsg)
+ return 0;
- sdt = paddr + i;
+ paddr = kmap_local_page(sg_page(psg)) + psg->offset;
+ daddr = kmap_local_page(sg_page(dsg)) + dsg->offset;
- pr_debug("DIF READ sector: %llu guard_tag: 0x%04x"
- " app_tag: 0x%04x ref_tag: %u\n",
- (unsigned long long)sector, sdt->guard_tag,
- sdt->app_tag, be32_to_cpu(sdt->ref_tag));
+ while (sector < start + sectors) {
+ struct t10_pi_tuple sdt;
+ __u16 crc;
+ unsigned int avail;
- if (sdt->app_tag == T10_PI_APP_ESCAPE) {
- dsg_off += block_size;
- goto next;
- }
+ if (poff >= psg->length && !sg_next(psg))
+ break;
+
+ if (!sbc_dif_prot_copy(&psg, &paddr, &poff, &dsg, &daddr,
+ &sdt, false)) {
+ cmd->sense_info = sector;
+ return TCM_LOGICAL_BLOCK_GUARD_CHECK_FAILED;
+ }
- avail = min(block_size, dsg->length - dsg_off);
- crc = crc_t10dif(daddr + dsg_off, avail);
- if (avail < block_size) {
- kunmap_atomic(daddr - dsg->offset);
- dsg = sg_next(dsg);
- if (!dsg) {
- kunmap_atomic(paddr - psg->offset);
- return 0;
- }
- daddr = kmap_atomic(sg_page(dsg)) + dsg->offset;
- dsg_off = block_size - avail;
- crc = crc_t10dif_update(crc, daddr, dsg_off);
- } else {
- dsg_off += block_size;
+ pr_debug("DIF READ sector: %llu guard_tag: 0x%04x app_tag: 0x%04x ref_tag: %u\n",
+ (unsigned long long)sector, sdt.guard_tag,
+ sdt.app_tag, be32_to_cpu(sdt.ref_tag));
+
+ if (sdt.app_tag == T10_PI_APP_ESCAPE) {
+ dsg_off += block_size;
+ goto next;
+ }
+
+ if (dsg_off >= dsg->length) {
+ dsg_off -= dsg->length;
+ kunmap_local(daddr - dsg->offset);
+ dsg = sg_next(dsg);
+ if (!dsg) {
+ kunmap_local(paddr - psg->offset);
+ return 0;
}
+ daddr = kmap_local_page(sg_page(dsg)) + dsg->offset;
+ }
- rc = sbc_dif_v1_verify(cmd, sdt, crc, sector, ei_lba);
- if (rc) {
- kunmap_atomic(daddr - dsg->offset);
- kunmap_atomic(paddr - psg->offset);
- cmd->sense_info = sector;
- return rc;
+ avail = min(block_size, dsg->length - dsg_off);
+ crc = crc_t10dif(daddr + dsg_off, avail);
+ if (avail < block_size) {
+ kunmap_local(daddr - dsg->offset);
+ dsg = sg_next(dsg);
+ if (!dsg) {
+ kunmap_local(paddr - psg->offset);
+ return 0;
}
-next:
- sector++;
- ei_lba++;
+ daddr = kmap_local_page(sg_page(dsg)) + dsg->offset;
+ dsg_off = block_size - avail;
+ crc = crc_t10dif_update(crc, daddr, dsg_off);
+ } else {
+ dsg_off += block_size;
}
- psg_off = 0;
- kunmap_atomic(daddr - dsg->offset);
- kunmap_atomic(paddr - psg->offset);
+ rc = sbc_dif_v1_verify(cmd, &sdt, crc, sector, ei_lba);
+ if (rc) {
+ kunmap_local(daddr - dsg->offset);
+ kunmap_local(paddr - psg->offset);
+ cmd->sense_info = sector;
+ return rc;
+ }
+next:
+ sector++;
+ ei_lba++;
}
+ kunmap_local(daddr - dsg->offset);
+ kunmap_local(paddr - psg->offset);
return 0;
}
EXPORT_SYMBOL(sbc_dif_verify);
--
2.34.1
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 6/8] scsi: target: limit DIF block CRC to each data sg
2026-10-06 9:33 [PATCH 0/8] scsi: target: keep command bytes inside the sg Jia Jia
` (4 preceding siblings ...)
2026-10-06 9:33 ` [PATCH 5/8] scsi: target: copy a DIF verify " Jia Jia
@ 2026-10-06 9:33 ` Jia Jia
2026-10-06 9:33 ` [PATCH 7/8] scsi: target: keep pscsi mode bytes inside the data sgs Jia Jia
2026-10-06 9:33 ` [PATCH 8/8] scsi: target: skip an escaped DIF block inside the data sg Jia Jia
7 siblings, 0 replies; 9+ messages in thread
From: Jia Jia @ 2026-10-06 9:33 UTC (permalink / raw)
To: Martin K . Petersen
Cc: Jan Engelhardt, Hannes Reinecke, Paolo Bonzini, Akinobu Mita,
James Bottomley, linux-scsi, target-devel, linux-kernel, Jia Jia
sbc_dif_generate() and sbc_dif_verify() CRC one logical block from the
data sg. When the first entry is shorter than the block, the remainder
is read from the next entry with
crc_t10dif_update(crc, daddr, block_size - avail)
That length is not limited to the next sg->length. A 512 byte block
split 256 + 100 + 156, with the 100 byte entry ending on a page, reads
156 bytes into the next physical page.
vhost-scsi can build that layout from one guest data buffer. Software
verify and software INSERT both use this CRC. Walk later entries and
read only the bytes each one actually holds. The helper unmaps the
current data page and may leave a later one mapped, with the same
kmap_local_page() calls as the rest of the walk.
KASAN reports:
BUG: KASAN: use-after-free in crc_t10dif_update+0x91/0xf0
Read of size 1
crc_t10dif_update
sbc_dif_verify
target_execute_cmd
vhost_scsi_write_pending
transport_generic_new_cmd
__target_submit
target_queued_submit_work
process_one_work
worker_thread
kthread
ret_from_fork
ret_from_fork_asm
Fixes: 18213afbd8ce ("target: handle odd SG mapping for data transfer memory")
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
drivers/target/target_core_sbc.c | 54 +++++++++++++++++++++++++-------
1 file changed, 42 insertions(+), 12 deletions(-)
diff --git a/drivers/target/target_core_sbc.c b/drivers/target/target_core_sbc.c
index c0aeb8886743..76dbc986e887 100644
--- a/drivers/target/target_core_sbc.c
+++ b/drivers/target/target_core_sbc.c
@@ -1310,6 +1310,44 @@ sbc_dif_prot_copy(struct scatterlist **psgp, void **paddrp,
return true;
}
+/*
+ * CRC the rest of one logical block. @need is the byte count still
+ * unread. Take only what each following data sg holds.
+ */
+static bool
+sbc_dif_crc_rest(struct scatterlist **dsgp, void **daddrp, int *offp,
+ unsigned int need, __u16 *crc)
+{
+ struct scatterlist *dsg = *dsgp;
+ void *daddr = *daddrp;
+
+ kunmap_local(daddr - dsg->offset);
+ while (need) {
+ unsigned int take;
+
+ dsg = sg_next(dsg);
+ if (!dsg)
+ return false;
+
+ daddr = kmap_local_page(sg_page(dsg)) + dsg->offset;
+ if (!dsg->length) {
+ kunmap_local(daddr - dsg->offset);
+ return false;
+ }
+
+ take = min_t(unsigned int, need, dsg->length);
+ *crc = crc_t10dif_update(*crc, daddr, take);
+ need -= take;
+ *offp = take;
+ if (need)
+ kunmap_local(daddr - dsg->offset);
+ }
+
+ *dsgp = dsg;
+ *daddrp = daddr;
+ return true;
+}
+
void
sbc_dif_generate(struct se_cmd *cmd)
{
@@ -1358,15 +1396,11 @@ sbc_dif_generate(struct se_cmd *cmd)
avail = min(block_size, dsg->length - offset);
crc = crc_t10dif(daddr + offset, avail);
if (avail < block_size) {
- kunmap_local(daddr - dsg->offset);
- dsg = sg_next(dsg);
- if (!dsg) {
+ if (!sbc_dif_crc_rest(&dsg, &daddr, &offset,
+ block_size - avail, &crc)) {
kunmap_local(paddr - psg->offset);
return;
}
- daddr = kmap_local_page(sg_page(dsg)) + dsg->offset;
- offset = block_size - avail;
- crc = crc_t10dif_update(crc, daddr, offset);
} else {
offset += block_size;
}
@@ -1543,15 +1577,11 @@ sbc_dif_verify(struct se_cmd *cmd, sector_t start, unsigned int sectors,
avail = min(block_size, dsg->length - dsg_off);
crc = crc_t10dif(daddr + dsg_off, avail);
if (avail < block_size) {
- kunmap_local(daddr - dsg->offset);
- dsg = sg_next(dsg);
- if (!dsg) {
+ if (!sbc_dif_crc_rest(&dsg, &daddr, &dsg_off,
+ block_size - avail, &crc)) {
kunmap_local(paddr - psg->offset);
return 0;
}
- daddr = kmap_local_page(sg_page(dsg)) + dsg->offset;
- dsg_off = block_size - avail;
- crc = crc_t10dif_update(crc, daddr, dsg_off);
} else {
dsg_off += block_size;
}
--
2.34.1
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 7/8] scsi: target: keep pscsi mode bytes inside the data sgs
2026-10-06 9:33 [PATCH 0/8] scsi: target: keep command bytes inside the sg Jia Jia
` (5 preceding siblings ...)
2026-10-06 9:33 ` [PATCH 6/8] scsi: target: limit DIF block CRC to each data sg Jia Jia
@ 2026-10-06 9:33 ` Jia Jia
2026-10-06 9:33 ` [PATCH 8/8] scsi: target: skip an escaped DIF block inside the data sg Jia Jia
7 siblings, 0 replies; 9+ messages in thread
From: Jia Jia @ 2026-10-06 9:33 UTC (permalink / raw)
To: Martin K . Petersen
Cc: Jan Engelhardt, Hannes Reinecke, Paolo Bonzini, Akinobu Mita,
James Bottomley, linux-scsi, target-devel, linux-kernel, Jia Jia
pscsi_complete_cmd() writes the write-protect bit at a fixed header
offset. MODE SENSE uses byte 2 and MODE SENSE (10) uses byte 3. The
allocation length is allowed to be shorter than that header. One byte
at page offset 4095 makes the store the next physical page.
On a tape device the same function then reads the MODE SELECT block
descriptor through sg_virt() of the first sg. MODE SELECT needs byte
11 and MODE SELECT (10) needs byte 15. A short first sg is not checked.
Read and write those bytes across the whole data sg list. A header
that spans entries is still applied. A buffer that ends first is left
unchanged.
Fixes: c66ac9db8d4a ("[SCSI] target: Add LIO target core v4.0.0-rc6")
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
drivers/target/target_core_pscsi.c | 98 +++++++++++++++++++++---------
1 file changed, 70 insertions(+), 28 deletions(-)
diff --git a/drivers/target/target_core_pscsi.c b/drivers/target/target_core_pscsi.c
index fd1b82fc7290..0e37a44f1e30 100644
--- a/drivers/target/target_core_pscsi.c
+++ b/drivers/target/target_core_pscsi.c
@@ -21,6 +21,7 @@
#include <linux/ratelimit.h>
#include <linux/module.h>
#include <linux/unaligned.h>
+#include <linux/highmem.h>
#include <scsi/scsi_device.h>
#include <scsi/scsi_host.h>
@@ -585,6 +586,51 @@ static void pscsi_destroy_device(struct se_device *dev)
}
}
+/*
+ * Copy @len bytes at data-buffer offset @off. @to_sg writes @buf into
+ * the sg. Stop when the command buffer or an sg runs out.
+ */
+static bool
+pscsi_copy_buf(struct se_cmd *cmd, unsigned int off, void *buf,
+ unsigned int len, bool to_sg)
+{
+ struct scatterlist *sg;
+ unsigned int i, skip = off;
+ u8 *p = buf;
+
+ if (!len)
+ return true;
+ if (!cmd->t_data_nents || !cmd->t_data_sg || off >= cmd->data_length ||
+ len > cmd->data_length - off)
+ return false;
+
+ for_each_sg(cmd->t_data_sg, sg, cmd->t_data_nents, i) {
+ unsigned int take;
+ void *addr;
+
+ if (!len)
+ return true;
+ if (skip >= sg->length) {
+ skip -= sg->length;
+ continue;
+ }
+
+ take = min_t(unsigned int, len, sg->length - skip);
+ addr = kmap_local_page(sg_page(sg));
+ addr += sg->offset + skip;
+ if (to_sg)
+ memcpy(addr, p, take);
+ else
+ memcpy(p, addr, take);
+ kunmap_local(addr);
+ p += take;
+ len -= take;
+ skip = 0;
+ }
+
+ return !len;
+}
+
static void pscsi_complete_cmd(struct se_cmd *cmd, u8 scsi_status,
unsigned char *req_sense, int valid_data)
{
@@ -610,21 +656,13 @@ static void pscsi_complete_cmd(struct se_cmd *cmd, u8 scsi_status,
bool read_only = target_lun_is_rdonly(cmd);
if (read_only) {
- unsigned char *buf;
-
- buf = transport_kmap_data_sg(cmd);
- if (!buf) {
- ; /* XXX: TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE */
- } else {
- if (cdb[0] == MODE_SENSE_10) {
- if (!(buf[3] & 0x80))
- buf[3] |= 0x80;
- } else {
- if (!(buf[2] & 0x80))
- buf[2] |= 0x80;
- }
+ unsigned char wp;
+ unsigned int wp_off = (cdb[0] == MODE_SENSE_10) ? 3 : 2;
- transport_kunmap_data_sg(cmd);
+ if (pscsi_copy_buf(cmd, wp_off, &wp, 1, false) &&
+ !(wp & 0x80)) {
+ wp |= 0x80;
+ pscsi_copy_buf(cmd, wp_off, &wp, 1, true);
}
}
}
@@ -643,28 +681,32 @@ static void pscsi_complete_cmd(struct se_cmd *cmd, u8 scsi_status,
*/
if (((cdb[0] == MODE_SELECT) || (cdb[0] == MODE_SELECT_10)) &&
scsi_status == SAM_STAT_GOOD) {
- unsigned char *buf;
+ unsigned char bdl_buf[2];
+ unsigned char bl[3];
u16 bdl;
u32 blocksize;
- buf = sg_virt(&cmd->t_data_sg[0]);
- if (!buf) {
- pr_err("Unable to get buf for scatterlist\n");
- goto after_mode_select;
+ if (cdb[0] == MODE_SELECT) {
+ if (!pscsi_copy_buf(cmd, 3, bdl_buf, 1, false))
+ goto after_mode_select;
+ bdl = bdl_buf[0];
+ } else {
+ if (!pscsi_copy_buf(cmd, 6, bdl_buf, 2, false))
+ goto after_mode_select;
+ bdl = get_unaligned_be16(bdl_buf);
}
- if (cdb[0] == MODE_SELECT)
- bdl = buf[3];
- else
- bdl = get_unaligned_be16(&buf[6]);
-
if (!bdl)
goto after_mode_select;
- if (cdb[0] == MODE_SELECT)
- blocksize = get_unaligned_be24(&buf[9]);
- else
- blocksize = get_unaligned_be24(&buf[13]);
+ if (cdb[0] == MODE_SELECT) {
+ if (!pscsi_copy_buf(cmd, 9, bl, 3, false))
+ goto after_mode_select;
+ } else {
+ if (!pscsi_copy_buf(cmd, 13, bl, 3, false))
+ goto after_mode_select;
+ }
+ blocksize = get_unaligned_be24(bl);
sd->sector_size = blocksize;
}
--
2.34.1
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 8/8] scsi: target: skip an escaped DIF block inside the data sg
2026-10-06 9:33 [PATCH 0/8] scsi: target: keep command bytes inside the sg Jia Jia
` (6 preceding siblings ...)
2026-10-06 9:33 ` [PATCH 7/8] scsi: target: keep pscsi mode bytes inside the data sgs Jia Jia
@ 2026-10-06 9:33 ` Jia Jia
7 siblings, 0 replies; 9+ messages in thread
From: Jia Jia @ 2026-10-06 9:33 UTC (permalink / raw)
To: Martin K . Petersen
Cc: Jan Engelhardt, Hannes Reinecke, Paolo Bonzini, Akinobu Mita,
James Bottomley, linux-scsi, target-devel, linux-kernel, Jia Jia
sbc_dif_verify() treats an application tag of 0xffff as one skipped
logical block and adds block_size to the data cursor. The next tuple
folds that cursor across a single sg entry. When the block spans more
entries, dsg_off remains past dsg->length and the following CRC reads a
full block from that offset.
A 512 byte block split 256 + 100 + the remainder, with the 100 byte
entry ending on a page and the first tuple escaped, reads the next
block from the next physical page.
Walk every data sg the skipped block covers. Those data pages are
mapped with kmap_local_page(), including the page the cursor moves to.
KASAN reports:
BUG: KASAN: use-after-free in crc_t10dif_update+0x91/0xf0
Read of size 1
crc_t10dif_update
sbc_dif_verify
target_execute_cmd
vhost_scsi_write_pending
transport_generic_new_cmd
__target_submit
target_queued_submit_work
process_one_work
worker_thread
kthread
ret_from_fork
ret_from_fork_asm
Fixes: 18213afbd8ce ("target: handle odd SG mapping for data transfer memory")
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
drivers/target/target_core_sbc.c | 46 +++++++++++++++++++++++++++++++-
1 file changed, 45 insertions(+), 1 deletion(-)
diff --git a/drivers/target/target_core_sbc.c b/drivers/target/target_core_sbc.c
index 76dbc986e887..7c4f66c2607a 100644
--- a/drivers/target/target_core_sbc.c
+++ b/drivers/target/target_core_sbc.c
@@ -1521,6 +1521,46 @@ void sbc_dif_copy_prot(struct se_cmd *cmd, unsigned int sectors, bool read,
}
EXPORT_SYMBOL(sbc_dif_copy_prot);
+/*
+ * Drop @len bytes of the data sg. An escaped PI tuple still covers one
+ * logical block, which may cross more than one sg entry.
+ */
+static bool
+sbc_dif_consume(struct scatterlist **dsgp, void **daddrp, int *offp,
+ unsigned int len)
+{
+ struct scatterlist *dsg = *dsgp;
+ void *daddr = *daddrp;
+ int off = *offp;
+
+ while (len) {
+ unsigned int take;
+
+ if (off >= dsg->length) {
+ kunmap_local(daddr - dsg->offset);
+ dsg = sg_next(dsg);
+ if (!dsg)
+ return false;
+
+ daddr = kmap_local_page(sg_page(dsg)) + dsg->offset;
+ off = 0;
+ if (!dsg->length) {
+ kunmap_local(daddr - dsg->offset);
+ return false;
+ }
+ }
+
+ take = min_t(unsigned int, len, dsg->length - off);
+ off += take;
+ len -= take;
+ }
+
+ *dsgp = dsg;
+ *daddrp = daddr;
+ *offp = off;
+ return true;
+}
+
sense_reason_t
sbc_dif_verify(struct se_cmd *cmd, sector_t start, unsigned int sectors,
unsigned int ei_lba, struct scatterlist *psg, int psg_off)
@@ -1559,7 +1599,11 @@ sbc_dif_verify(struct se_cmd *cmd, sector_t start, unsigned int sectors,
sdt.app_tag, be32_to_cpu(sdt.ref_tag));
if (sdt.app_tag == T10_PI_APP_ESCAPE) {
- dsg_off += block_size;
+ if (!sbc_dif_consume(&dsg, &daddr, &dsg_off,
+ block_size)) {
+ kunmap_local(paddr - psg->offset);
+ return 0;
+ }
goto next;
}
--
2.34.1
^ permalink raw reply [flat|nested] 9+ messages in thread