Skip to content

Commit 01d4d67

Browse files
author
Nicholas Bellinger
committed
target: Fix multi-session dynamic se_node_acl double free OOPs
This patch addresses a long-standing bug with multi-session (eg: iscsi-target + iser-target) se_node_acl dynamic free withini transport_deregister_session(). This bug is caused when a storage endpoint is configured with demo-mode (generate_node_acls = 1 + cache_dynamic_acls = 1) initiators, and initiator login creates a new dynamic node acl and attaches two sessions to it. After that, demo-mode for the storage instance is disabled via configfs (generate_node_acls = 0 + cache_dynamic_acls = 0) and the existing dynamic acl is never converted to an explicit ACL. The end result is dynamic acl resources are released twice when the sessions are shutdown in transport_deregister_session(). If the storage instance is not changed to disable demo-mode, or the dynamic acl is converted to an explict ACL, or there is only a single session associated with the dynamic ACL, the bug is not triggered. To address this big, move the release of dynamic se_node_acl memory into target_complete_nacl() so it's only freed once when se_node_acl->acl_kref reaches zero. (Drop unnecessary list_del_init usage - HCH) Reported-by: Rob Millner <[email protected]> Tested-by: Rob Millner <[email protected]> Cc: Rob Millner <[email protected]> Cc: [email protected] # 4.1+ Signed-off-by: Nicholas Bellinger <[email protected]>
1 parent c54eeff commit 01d4d67

File tree

2 files changed

+44
-26
lines changed

2 files changed

+44
-26
lines changed

drivers/target/target_core_transport.c

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -457,8 +457,20 @@ static void target_complete_nacl(struct kref *kref)
457457
{
458458
struct se_node_acl *nacl = container_of(kref,
459459
struct se_node_acl, acl_kref);
460+
struct se_portal_group *se_tpg = nacl->se_tpg;
460461

461-
complete(&nacl->acl_free_comp);
462+
if (!nacl->dynamic_stop) {
463+
complete(&nacl->acl_free_comp);
464+
return;
465+
}
466+
467+
mutex_lock(&se_tpg->acl_node_mutex);
468+
list_del(&nacl->acl_list);
469+
mutex_unlock(&se_tpg->acl_node_mutex);
470+
471+
core_tpg_wait_for_nacl_pr_ref(nacl);
472+
core_free_device_list_for_node(nacl, se_tpg);
473+
kfree(nacl);
462474
}
463475

464476
void target_put_nacl(struct se_node_acl *nacl)
@@ -499,12 +511,39 @@ EXPORT_SYMBOL(transport_deregister_session_configfs);
499511
void transport_free_session(struct se_session *se_sess)
500512
{
501513
struct se_node_acl *se_nacl = se_sess->se_node_acl;
514+
502515
/*
503516
* Drop the se_node_acl->nacl_kref obtained from within
504517
* core_tpg_get_initiator_node_acl().
505518
*/
506519
if (se_nacl) {
520+
struct se_portal_group *se_tpg = se_nacl->se_tpg;
521+
const struct target_core_fabric_ops *se_tfo = se_tpg->se_tpg_tfo;
522+
unsigned long flags;
523+
507524
se_sess->se_node_acl = NULL;
525+
526+
/*
527+
* Also determine if we need to drop the extra ->cmd_kref if
528+
* it had been previously dynamically generated, and
529+
* the endpoint is not caching dynamic ACLs.
530+
*/
531+
mutex_lock(&se_tpg->acl_node_mutex);
532+
if (se_nacl->dynamic_node_acl &&
533+
!se_tfo->tpg_check_demo_mode_cache(se_tpg)) {
534+
spin_lock_irqsave(&se_nacl->nacl_sess_lock, flags);
535+
if (list_empty(&se_nacl->acl_sess_list))
536+
se_nacl->dynamic_stop = true;
537+
spin_unlock_irqrestore(&se_nacl->nacl_sess_lock, flags);
538+
539+
if (se_nacl->dynamic_stop)
540+
list_del(&se_nacl->acl_list);
541+
}
542+
mutex_unlock(&se_tpg->acl_node_mutex);
543+
544+
if (se_nacl->dynamic_stop)
545+
target_put_nacl(se_nacl);
546+
508547
target_put_nacl(se_nacl);
509548
}
510549
if (se_sess->sess_cmd_map) {
@@ -518,50 +557,28 @@ EXPORT_SYMBOL(transport_free_session);
518557
void transport_deregister_session(struct se_session *se_sess)
519558
{
520559
struct se_portal_group *se_tpg = se_sess->se_tpg;
521-
const struct target_core_fabric_ops *se_tfo;
522-
struct se_node_acl *se_nacl;
523560
unsigned long flags;
524-
bool drop_nacl = false;
525561

526562
if (!se_tpg) {
527563
transport_free_session(se_sess);
528564
return;
529565
}
530-
se_tfo = se_tpg->se_tpg_tfo;
531566

532567
spin_lock_irqsave(&se_tpg->session_lock, flags);
533568
list_del(&se_sess->sess_list);
534569
se_sess->se_tpg = NULL;
535570
se_sess->fabric_sess_ptr = NULL;
536571
spin_unlock_irqrestore(&se_tpg->session_lock, flags);
537572

538-
/*
539-
* Determine if we need to do extra work for this initiator node's
540-
* struct se_node_acl if it had been previously dynamically generated.
541-
*/
542-
se_nacl = se_sess->se_node_acl;
543-
544-
mutex_lock(&se_tpg->acl_node_mutex);
545-
if (se_nacl && se_nacl->dynamic_node_acl) {
546-
if (!se_tfo->tpg_check_demo_mode_cache(se_tpg)) {
547-
list_del(&se_nacl->acl_list);
548-
drop_nacl = true;
549-
}
550-
}
551-
mutex_unlock(&se_tpg->acl_node_mutex);
552-
553-
if (drop_nacl) {
554-
core_tpg_wait_for_nacl_pr_ref(se_nacl);
555-
core_free_device_list_for_node(se_nacl, se_tpg);
556-
se_sess->se_node_acl = NULL;
557-
kfree(se_nacl);
558-
}
559573
pr_debug("TARGET_CORE[%s]: Deregistered fabric_sess\n",
560574
se_tpg->se_tpg_tfo->get_fabric_name());
561575
/*
562576
* If last kref is dropping now for an explicit NodeACL, awake sleeping
563577
* ->acl_free_comp caller to wakeup configfs se_node_acl->acl_group
564578
* removal context from within transport_free_session() code.
579+
*
580+
* For dynamic ACL, target_put_nacl() uses target_complete_nacl()
581+
* to release all remaining generate_node_acl=1 created ACL resources.
565582
*/
566583

567584
transport_free_session(se_sess);

include/target/target_core_base.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,7 @@ struct se_node_acl {
538538
char initiatorname[TRANSPORT_IQN_LEN];
539539
/* Used to signal demo mode created ACL, disabled by default */
540540
bool dynamic_node_acl;
541+
bool dynamic_stop;
541542
u32 queue_depth;
542543
u32 acl_index;
543544
enum target_prot_type saved_prot_type;

0 commit comments

Comments
 (0)