[PATCH 4/8] scsi: target: copy a DIF insert tuple across prot sgs

From: Jia Jia

Date: Tue Oct 06 2026 - 05:35:00 EST


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