[PATCH 5/8] scsi: target: copy a DIF verify tuple across prot sgs

From: Jia Jia

Date: Tue Oct 06 2026 - 05:37:47 EST


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@xxxxxxxxx>
---
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