[PATCH 6/8] scsi: target: limit DIF block CRC to each data sg
From: Jia Jia
Date: Tue Oct 06 2026 - 05:37:43 EST
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@xxxxxxxxx>
---
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