From: sashiko-bot@kernel.org
To: "Yu Zhang" <yuz08559@gmail.com>
Cc: target-devel@vger.kernel.org, linux-scsi@vger.kernel.org
Subject: Re: [PATCH] scsi: target: fix list corruption in core_alua_free_tg_pt_gp()
Date: Mon, 19 Oct 2026 03:51:07 +0000 [thread overview]
Message-ID: <sashiko-outbox-176590@kernel.org> (raw)
In-Reply-To: <20261019033532.73575-1-yuz08559@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] scsi: target: list corruption on next LUN in core_alua_free_tg_pt_gp()
- [Critical] scsi: target: use-after-free of se_lun in core_alua_free_tg_pt_gp()
--
commit 9207913678d53d80f463a158ebf713e95fda2ea5
Author: Yu Zhang <yuz08559@gmail.com>
scsi: target: fix list corruption in core_alua_free_tg_pt_gp()
This commit attempts to fix a list corruption race in
core_alua_free_tg_pt_gp() by re-checking under lun->lun_tg_pt_gp_lock
whether lun->lun_tg_pt_gp still points to the group being freed before
reattaching the LUN to the default group.
> diff --git a/drivers/target/target_core_alua.c b/drivers/target/target_core_alua.c
> index 140154d93c430..24fafcb2809fa 100644
> --- a/drivers/target/target_core_alua.c
> +++ b/drivers/target/target_core_alua.c
> @@ -1755,16 +1755,23 @@ void core_alua_free_tg_pt_gp(
>
> spin_unlock(&tg_pt_gp->tg_pt_gp_lock);
> /*
> - * If the passed tg_pt_gp does NOT match the default_tg_pt_gp,
> - * assume we want to re-associate a given tg_pt_gp_mem with
> - * default_tg_pt_gp.
> + * tg_pt_gp_lock was dropped above, so a concurrent
> + * core_alua_store_tg_pt_gp_info() may already have moved this
> + * lun to another group and re-added lun_tg_pt_gp_link to that
> + * group's list. Reattach to the default group only while the
> + * lun still references the group being freed; otherwise the
> + * bare list_add_tail() in __target_attach_tg_pt_gp() would add
> + * the already-linked node a second time and corrupt the list.
> */
> spin_lock(&lun->lun_tg_pt_gp_lock);
[Severity: Critical]
Could this trigger a use-after-free on lun when tg_pt_gp->tg_pt_gp_lock is
dropped?
In drivers/target/target_core_alua.c:core_alua_free_tg_pt_gp(), the loop
unlinks lun from tg_pt_gp_lun_list and drops tg_pt_gp->tg_pt_gp_lock
without holding rcu_read_lock() or pinning lun->lun_ref (unlike
core_alua_queue_state_change_ua(), which pins lun via
percpu_ref_tryget_live(&lun->lun_ref) before dropping tg_pt_gp_lock).
While tg_pt_gp->tg_pt_gp_lock is dropped, a concurrent configfs unlink of
the port symlink and rmdir of the LUN directory can detach and free lun
before core_alua_free_tg_pt_gp() reaches spin_lock(&lun->lun_tg_pt_gp_lock):
configfs_unlink()
target_fabric_port_unlink()
core_dev_del_lun()
core_tpg_remove_lun()
transport_clear_lun_ref(lun)
target_detach_tg_pt_gp(lun)
configfs_rmdir()
target_fabric_drop_lun()
target_fabric_port_release()
call_rcu(&lun->rcu_head, target_tpg_free_lun)
target_tpg_free_lun()
kfree(lun)
When core_alua_free_tg_pt_gp() resumes, wouldn't spin_lock(&lun->lun_tg_pt_gp_lock)
and rcu_dereference_check(lun->lun_tg_pt_gp, ...) dereference freed
struct se_lun memory?
> - if (tg_pt_gp != dev->t10_alua.default_tg_pt_gp) {
> - __target_attach_tg_pt_gp(lun,
> - dev->t10_alua.default_tg_pt_gp);
> - } else
> - rcu_assign_pointer(lun->lun_tg_pt_gp, NULL);
> + if (rcu_dereference_check(lun->lun_tg_pt_gp,
> + lockdep_is_held(&lun->lun_tg_pt_gp_lock)) == tg_pt_gp) {
> + if (tg_pt_gp != dev->t10_alua.default_tg_pt_gp)
> + __target_attach_tg_pt_gp(lun,
> + dev->t10_alua.default_tg_pt_gp);
> + else
> + rcu_assign_pointer(lun->lun_tg_pt_gp, NULL);
> + }
> spin_unlock(&lun->lun_tg_pt_gp_lock);
>
> spin_lock(&tg_pt_gp->tg_pt_gp_lock);
[Severity: Critical]
What happens to the pre-fetched next pointer in list_for_each_entry_safe()
when tg_pt_gp->tg_pt_gp_lock is dropped across this window?
In drivers/target/target_core_alua.c:core_alua_free_tg_pt_gp():
spin_lock(&tg_pt_gp->tg_pt_gp_lock);
list_for_each_entry_safe(lun, next,
&tg_pt_gp->tg_pt_gp_lun_list, lun_tg_pt_gp_link) {
list_del_init(&lun->lun_tg_pt_gp_link);
tg_pt_gp->tg_pt_gp_members--;
spin_unlock(&tg_pt_gp->tg_pt_gp_lock);
If tg_pt_gp_lun_list has two LUNs (lun1 and lun2), iteration 1 sets
lun = lun1, caches next = lun2, and drops tg_pt_gp->tg_pt_gp_lock.
While that lock is dropped, a concurrent core_alua_store_tg_pt_gp_info()
on lun2 can acquire lun2->lun_tg_pt_gp_lock and call
target_swap_tg_pt_gp(lun2, tg_pt_gp, tg_pt_gp_new), which moves
lun2->lun_tg_pt_gp_link into tg_pt_gp_new->tg_pt_gp_lun_list and sets
lun2->lun_tg_pt_gp = tg_pt_gp_new.
When core_alua_free_tg_pt_gp() reacquires tg_pt_gp->tg_pt_gp_lock and
advances to iteration 2 with the stale next (lun2):
1. list_for_each_entry_safe() sets next = list_next_entry(lun2, lun_tg_pt_gp_link),
which now points into tg_pt_gp_new->tg_pt_gp_lun_list.
2. The loop body runs list_del_init(&lun2->lun_tg_pt_gp_link) and
tg_pt_gp->tg_pt_gp_members-- without holding tg_pt_gp_new->tg_pt_gp_lock,
corrupting tg_pt_gp_new->tg_pt_gp_lun_list.
3. Under lun2->lun_tg_pt_gp_lock, lun2->lun_tg_pt_gp == tg_pt_gp is false,
so reattachment is skipped and lun2 is left unlinked while
lun2->lun_tg_pt_gp still points to tg_pt_gp_new.
4. Subsequent iterations walk tg_pt_gp_new->tg_pt_gp_lun_list; because
&tg_pt_gp_new->tg_pt_gp_lun_list != &tg_pt_gp->tg_pt_gp_lun_list, the
loop does not terminate at tg_pt_gp_new's list head and treats
container_of(&tg_pt_gp_new->tg_pt_gp_lun_list, struct se_lun, lun_tg_pt_gp_link)
as a struct se_lun.
Similarly, if lun2 is concurrently detached via target_detach_tg_pt_gp(lun2)
while tg_pt_gp->tg_pt_gp_lock is dropped for lun1, list_del_init() points
lun2->lun_tg_pt_gp_link at itself, causing list_for_each_entry_safe() to
spin on lun2 indefinitely.
In addition, even with a single LUN, because list_del_init(&lun->lun_tg_pt_gp_link)
and tg_pt_gp->tg_pt_gp_members-- run before dropping tg_pt_gp->tg_pt_gp_lock
while lun->lun_tg_pt_gp still points to tg_pt_gp, won't a concurrent
core_alua_store_tg_pt_gp_info() -> target_swap_tg_pt_gp() ->
__target_detach_tg_pt_gp() in that window still decrement
tg_pt_gp->tg_pt_gp_members a second time?
霸
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261019033532.73575-1-yuz08559@gmail.com?part=1
prev parent reply other threads:[~2026-10-19 3:51 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-19 3:35 [PATCH] scsi: target: fix list corruption in core_alua_free_tg_pt_gp() Yu Zhang
2026-10-19 3:51 ` sashiko-bot [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-176590@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=linux-scsi@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=target-devel@vger.kernel.org \
--cc=yuz08559@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.