diff options
author | Nicholas Bellinger <nab@linux-iscsi.org> | 2016-12-07 15:55:54 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2017-02-14 18:25:36 -0500 |
commit | 4978149de58d816f101daabaf089464b6108ad84 (patch) | |
tree | 98c921580a62dea3424ac5b3ada50f11cfef8617 | |
parent | 88e865c5d813e400ad0b135c39fe5842e95df6f4 (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.c | 69 | ||||
-rw-r--r-- | include/target/target_core_base.h | 1 |
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 | ||
464 | void target_put_nacl(struct se_node_acl *nacl) | 476 | void target_put_nacl(struct se_node_acl *nacl) |
@@ -499,12 +511,39 @@ EXPORT_SYMBOL(transport_deregister_session_configfs); | |||
499 | void transport_free_session(struct se_session *se_sess) | 511 | void 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); | |||
518 | void transport_deregister_session(struct se_session *se_sess) | 557 | void 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; |