aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicholas Bellinger <nab@linux-iscsi.org>2016-12-07 15:55:54 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2017-02-14 18:25:36 -0500
commit4978149de58d816f101daabaf089464b6108ad84 (patch)
tree98c921580a62dea3424ac5b3ada50f11cfef8617
parent88e865c5d813e400ad0b135c39fe5842e95df6f4 (diff)
target: Fix multi-session dynamic se_node_acl double free OOPs
commit 01d4d673558985d9a118e1e05026633c3e2ade9b upstream. 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 <rlm@daterainc.com> Tested-by: Rob Millner <rlm@daterainc.com> Cc: Rob Millner <rlm@daterainc.com> Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/target/target_core_transport.c69
-rw-r--r--include/target/target_core_base.h1
2 files changed, 44 insertions, 26 deletions
diff --git a/drivers/target/target_core_transport.c b/drivers/target/target_core_transport.c
index f5aed8901d70..767d1eb6e035 100644
--- a/drivers/target/target_core_transport.c
+++ b/drivers/target/target_core_transport.c
@@ -457,8 +457,20 @@ static void target_complete_nacl(struct kref *kref)
457{ 457{
458 struct se_node_acl *nacl = container_of(kref, 458 struct se_node_acl *nacl = container_of(kref,
459 struct se_node_acl, acl_kref); 459 struct se_node_acl, acl_kref);
460 struct se_portal_group *se_tpg = nacl->se_tpg;
460 461
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);
462} 474}
463 475
464void target_put_nacl(struct se_node_acl *nacl) 476void target_put_nacl(struct se_node_acl *nacl)
@@ -499,12 +511,39 @@ EXPORT_SYMBOL(transport_deregister_session_configfs);
499void transport_free_session(struct se_session *se_sess) 511void transport_free_session(struct se_session *se_sess)
500{ 512{
501 struct se_node_acl *se_nacl = se_sess->se_node_acl; 513 struct se_node_acl *se_nacl = se_sess->se_node_acl;
514
502 /* 515 /*
503 * Drop the se_node_acl->nacl_kref obtained from within 516 * Drop the se_node_acl->nacl_kref obtained from within
504 * core_tpg_get_initiator_node_acl(). 517 * core_tpg_get_initiator_node_acl().
505 */ 518 */
506 if (se_nacl) { 519 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
507 se_sess->se_node_acl = NULL; 524 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
508 target_put_nacl(se_nacl); 547 target_put_nacl(se_nacl);
509 } 548 }
510 if (se_sess->sess_cmd_map) { 549 if (se_sess->sess_cmd_map) {
@@ -518,16 +557,12 @@ EXPORT_SYMBOL(transport_free_session);
518void transport_deregister_session(struct se_session *se_sess) 557void transport_deregister_session(struct se_session *se_sess)
519{ 558{
520 struct se_portal_group *se_tpg = se_sess->se_tpg; 559 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;
523 unsigned long flags; 560 unsigned long flags;
524 bool drop_nacl = false;
525 561
526 if (!se_tpg) { 562 if (!se_tpg) {
527 transport_free_session(se_sess); 563 transport_free_session(se_sess);
528 return; 564 return;
529 } 565 }
530 se_tfo = se_tpg->se_tpg_tfo;
531 566
532 spin_lock_irqsave(&se_tpg->session_lock, flags); 567 spin_lock_irqsave(&se_tpg->session_lock, flags);
533 list_del(&se_sess->sess_list); 568 list_del(&se_sess->sess_list);
@@ -535,33 +570,15 @@ void transport_deregister_session(struct se_session *se_sess)
535 se_sess->fabric_sess_ptr = NULL; 570 se_sess->fabric_sess_ptr = NULL;
536 spin_unlock_irqrestore(&se_tpg->session_lock, flags); 571 spin_unlock_irqrestore(&se_tpg->session_lock, flags);
537 572
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 }
559 pr_debug("TARGET_CORE[%s]: Deregistered fabric_sess\n", 573 pr_debug("TARGET_CORE[%s]: Deregistered fabric_sess\n",
560 se_tpg->se_tpg_tfo->get_fabric_name()); 574 se_tpg->se_tpg_tfo->get_fabric_name());
561 /* 575 /*
562 * If last kref is dropping now for an explicit NodeACL, awake sleeping 576 * If last kref is dropping now for an explicit NodeACL, awake sleeping
563 * ->acl_free_comp caller to wakeup configfs se_node_acl->acl_group 577 * ->acl_free_comp caller to wakeup configfs se_node_acl->acl_group
564 * removal context from within transport_free_session() code. 578 * 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.
565 */ 582 */
566 583
567 transport_free_session(se_sess); 584 transport_free_session(se_sess);
diff --git a/include/target/target_core_base.h b/include/target/target_core_base.h
index c2119008990a..48bc1ac1da43 100644
--- a/include/target/target_core_base.h
+++ b/include/target/target_core_base.h
@@ -538,6 +538,7 @@ struct se_node_acl {
538 char initiatorname[TRANSPORT_IQN_LEN]; 538 char initiatorname[TRANSPORT_IQN_LEN];
539 /* Used to signal demo mode created ACL, disabled by default */ 539 /* Used to signal demo mode created ACL, disabled by default */
540 bool dynamic_node_acl; 540 bool dynamic_node_acl;
541 bool dynamic_stop;
541 u32 queue_depth; 542 u32 queue_depth;
542 u32 acl_index; 543 u32 acl_index;
543 enum target_prot_type saved_prot_type; 544 enum target_prot_type saved_prot_type;