target_emulate_set_target_port_groups() handles a SECONDARY target-port asymmetric access state (OFFLINE) by walking dev->dev_sep_list under dev->se_port_lock to find the se_lun whose relative target port identifier matches the request, then dropping se_port_lock and calling core_alua_set_tg_pt_secondary_state() on that lun: list_for_each_entry(lun, &dev->dev_sep_list, lun_dev_link) { if (lun->lun_tpg->tpg_rtpi != rtpi) continue; // XXX: racy unlock spin_unlock(&dev->se_port_lock); if (!core_alua_set_tg_pt_secondary_state(lun, 1, 1)) found = true; spin_lock(&dev->se_port_lock); break; } core_alua_set_tg_pt_secondary_state() sleeps while using the lun -- msleep(trans_delay_msecs) and, when alua_write_metadata is set, a kernel_write() of the ALUA metadata. The matched lun is a different target port than the one the command arrived on, so it is not pinned by the command's own lun_ref. A concurrent core_tpg_remove_lun() of that port (configfs LUN removal) drains its lun_ref, removes it from dev_sep_list and frees the se_lun via call_rcu(); since the command holds no reference and is not in an RCU read-side section across the sleep, the grace period can elapse and the lun is freed under it: BUG: KASAN: slab-use-after-free in core_alua_set_tg_pt_secondary_state core_alua_set_tg_pt_secondary_state target_emulate_set_target_port_groups __target_execute_cmd Allocated by task 3486: configfs_mkdir Freed by task 0: __call_rcu_common Take the matched lun's lun_ref with percpu_ref_tryget_live() before dropping se_port_lock and release it after the transition, mirroring core_alua_queue_state_change_ua(). This pins the se_lun across the sleep, so a concurrent LUN removal waits in transport_clear_lun_ref() instead of freeing it. Reproduced with two tcm_loop target ports (distinct RTPIs) on one backstore, explicit ALUA, large trans_delay_msecs: a SET TARGET PORT GROUPS (secondary OFFLINE, targeting the second port's RTPI) on the first port races a configfs removal of the second port's LUN -> the KASAN splat above; with this patch the LUN removal blocks until the transition completes and there is no use-after-free. Fixes: adf653f92f38e8 ("target: Subsume se_port + t10_alua_tg_pt_gp_member into se_lun") Cc: stable@vger.kernel.org Signed-off-by: Yu Zhang --- drivers/target/target_core_alua.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/target/target_core_alua.c b/drivers/target/target_core_alua.c index 1111111..2222222 100644 --- a/drivers/target/target_core_alua.c +++ b/drivers/target/target_core_alua.c @@ -402,13 +402,16 @@ if (lun->lun_tpg->tpg_rtpi != rtpi) continue; - // XXX: racy unlock + if (!percpu_ref_tryget_live(&lun->lun_ref)) + continue; + spin_unlock(&dev->se_port_lock); if (!core_alua_set_tg_pt_secondary_state( lun, 1, 1)) found = true; + percpu_ref_put(&lun->lun_ref); spin_lock(&dev->se_port_lock); break; }