[PATCH 2/8] scsi: target: keep REPORT REFERRALS stores inside the buffer

From: Jia Jia

Date: Tue Oct 06 2026 - 05:36:01 EST


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