Skip to content

Commit bd4e2d2

Browse files
author
Nicholas Bellinger
committed
target: Fix NULL dereference during LUN lookup + active I/O shutdown
When transport_clear_lun_ref() is shutting down a se_lun via configfs with new I/O in-flight, it's possible to trigger a NULL pointer dereference in transport_lookup_cmd_lun() due to the fact percpu_ref_get() doesn't do any __PERCPU_REF_DEAD checking before incrementing lun->lun_ref.count after lun->lun_ref has switched to atomic_t mode. This results in a NULL pointer dereference as LUN shutdown code in core_tpg_remove_lun() continues running after the existing ->release() -> core_tpg_lun_ref_release() callback completes, and clears the RCU protected se_lun->lun_se_dev pointer. During the OOPs, the state of lun->lun_ref in the process which triggered the NULL pointer dereference looks like the following on v4.1.y stable code: struct se_lun { lun_link_magic = 4294932337, lun_status = TRANSPORT_LUN_STATUS_FREE, ..... lun_se_dev = 0x0, lun_sep = 0x0, ..... lun_ref = { count = { counter = 1 }, percpu_count_ptr = 3, release = 0xffffffffa02fa1e0 <core_tpg_lun_ref_release>, confirm_switch = 0x0, force_atomic = false, rcu = { next = 0xffff88154fa1a5d0, func = 0xffffffff8137c4c0 <percpu_ref_switch_to_atomic_rcu> } } } To address this bug, use percpu_ref_tryget_live() to ensure once __PERCPU_REF_DEAD is visable on all CPUs and ->lun_ref has switched to atomic_t, all new I/Os will fail to obtain a new lun->lun_ref reference. Also use an explicit percpu_ref_kill_and_confirm() callback to block on ->lun_ref_comp to allow the first stage and associated RCU grace period to complete, and then block on ->lun_ref_shutdown waiting for the final percpu_ref_put() to drop the last reference via transport_lun_remove_cmd() before continuing with core_tpg_remove_lun() shutdown. Reported-by: Rob Millner <[email protected]> Tested-by: Rob Millner <[email protected]> Cc: Rob Millner <[email protected]> Tested-by: Vaibhav Tandon <[email protected]> Cc: Vaibhav Tandon <[email protected]> Tested-by: Bryant G. Ly <[email protected]> Cc: <[email protected]> # v3.14+ Signed-off-by: Nicholas Bellinger <[email protected]>
1 parent 51ec502 commit bd4e2d2

File tree

4 files changed

+41
-4
lines changed

4 files changed

+41
-4
lines changed

drivers/target/target_core_device.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,16 @@ transport_lookup_cmd_lun(struct se_cmd *se_cmd, u64 unpacked_lun)
7878
&deve->read_bytes);
7979

8080
se_lun = rcu_dereference(deve->se_lun);
81+
82+
if (!percpu_ref_tryget_live(&se_lun->lun_ref)) {
83+
se_lun = NULL;
84+
goto out_unlock;
85+
}
86+
8187
se_cmd->se_lun = rcu_dereference(deve->se_lun);
8288
se_cmd->pr_res_key = deve->pr_res_key;
8389
se_cmd->orig_fe_lun = unpacked_lun;
8490
se_cmd->se_cmd_flags |= SCF_SE_LUN_CMD;
85-
86-
percpu_ref_get(&se_lun->lun_ref);
8791
se_cmd->lun_ref_active = true;
8892

8993
if ((se_cmd->data_direction == DMA_TO_DEVICE) &&
@@ -97,6 +101,7 @@ transport_lookup_cmd_lun(struct se_cmd *se_cmd, u64 unpacked_lun)
97101
goto ref_dev;
98102
}
99103
}
104+
out_unlock:
100105
rcu_read_unlock();
101106

102107
if (!se_lun) {
@@ -815,6 +820,7 @@ struct se_device *target_alloc_device(struct se_hba *hba, const char *name)
815820
xcopy_lun = &dev->xcopy_lun;
816821
rcu_assign_pointer(xcopy_lun->lun_se_dev, dev);
817822
init_completion(&xcopy_lun->lun_ref_comp);
823+
init_completion(&xcopy_lun->lun_shutdown_comp);
818824
INIT_LIST_HEAD(&xcopy_lun->lun_deve_list);
819825
INIT_LIST_HEAD(&xcopy_lun->lun_dev_link);
820826
mutex_init(&xcopy_lun->lun_tg_pt_md_mutex);

drivers/target/target_core_tpg.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ static void core_tpg_lun_ref_release(struct percpu_ref *ref)
445445
{
446446
struct se_lun *lun = container_of(ref, struct se_lun, lun_ref);
447447

448-
complete(&lun->lun_ref_comp);
448+
complete(&lun->lun_shutdown_comp);
449449
}
450450

451451
int core_tpg_register(
@@ -571,6 +571,7 @@ struct se_lun *core_tpg_alloc_lun(
571571
lun->lun_link_magic = SE_LUN_LINK_MAGIC;
572572
atomic_set(&lun->lun_acl_count, 0);
573573
init_completion(&lun->lun_ref_comp);
574+
init_completion(&lun->lun_shutdown_comp);
574575
INIT_LIST_HEAD(&lun->lun_deve_list);
575576
INIT_LIST_HEAD(&lun->lun_dev_link);
576577
atomic_set(&lun->lun_tg_pt_secondary_offline, 0);

drivers/target/target_core_transport.c

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2700,10 +2700,39 @@ void target_wait_for_sess_cmds(struct se_session *se_sess)
27002700
}
27012701
EXPORT_SYMBOL(target_wait_for_sess_cmds);
27022702

2703+
static void target_lun_confirm(struct percpu_ref *ref)
2704+
{
2705+
struct se_lun *lun = container_of(ref, struct se_lun, lun_ref);
2706+
2707+
complete(&lun->lun_ref_comp);
2708+
}
2709+
27032710
void transport_clear_lun_ref(struct se_lun *lun)
27042711
{
2705-
percpu_ref_kill(&lun->lun_ref);
2712+
/*
2713+
* Mark the percpu-ref as DEAD, switch to atomic_t mode, drop
2714+
* the initial reference and schedule confirm kill to be
2715+
* executed after one full RCU grace period has completed.
2716+
*/
2717+
percpu_ref_kill_and_confirm(&lun->lun_ref, target_lun_confirm);
2718+
/*
2719+
* The first completion waits for percpu_ref_switch_to_atomic_rcu()
2720+
* to call target_lun_confirm after lun->lun_ref has been marked
2721+
* as __PERCPU_REF_DEAD on all CPUs, and switches to atomic_t
2722+
* mode so that percpu_ref_tryget_live() lookup of lun->lun_ref
2723+
* fails for all new incoming I/O.
2724+
*/
27062725
wait_for_completion(&lun->lun_ref_comp);
2726+
/*
2727+
* The second completion waits for percpu_ref_put_many() to
2728+
* invoke ->release() after lun->lun_ref has switched to
2729+
* atomic_t mode, and lun->lun_ref.count has reached zero.
2730+
*
2731+
* At this point all target-core lun->lun_ref references have
2732+
* been dropped via transport_lun_remove_cmd(), and it's safe
2733+
* to proceed with the remaining LUN shutdown.
2734+
*/
2735+
wait_for_completion(&lun->lun_shutdown_comp);
27072736
}
27082737

27092738
static bool

include/target/target_core_base.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,7 @@ struct se_lun {
730730
struct config_group lun_group;
731731
struct se_port_stat_grps port_stat_grps;
732732
struct completion lun_ref_comp;
733+
struct completion lun_shutdown_comp;
733734
struct percpu_ref lun_ref;
734735
struct list_head lun_dev_link;
735736
struct hlist_node link;

0 commit comments

Comments
 (0)