From: Greg Kroah-Hartman Date: Thu, 9 Mar 2017 06:43:59 +0000 (+0100) Subject: 4.4-stable patches X-Git-Tag: v4.4.53~14 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ac1d978973ac62a1e401a13936b4caef0c762973;p=thirdparty%2Fkernel%2Fstable-queue.git 4.4-stable patches added patches: target-fix-multi-session-dynamic-se_node_acl-double-free-oops.patch target-obtain-se_node_acl-acl_kref-during-get_initiator_node_acl.patch --- diff --git a/queue-4.4/series b/queue-4.4/series index 143fe97a749..659099c7b29 100644 --- a/queue-4.4/series +++ b/queue-4.4/series @@ -49,3 +49,5 @@ ext4-fix-inline-data-error-paths.patch ext4-preserve-the-needs_recovery-flag-when-the-journal-is-aborted.patch ext4-return-erofs-if-device-is-r-o-and-journal-replay-is-needed.patch samples-seccomp-fix-64-bit-comparison-macros.patch +target-obtain-se_node_acl-acl_kref-during-get_initiator_node_acl.patch +target-fix-multi-session-dynamic-se_node_acl-double-free-oops.patch diff --git a/queue-4.4/target-fix-multi-session-dynamic-se_node_acl-double-free-oops.patch b/queue-4.4/target-fix-multi-session-dynamic-se_node_acl-double-free-oops.patch new file mode 100644 index 00000000000..2a3f0d3e8a6 --- /dev/null +++ b/queue-4.4/target-fix-multi-session-dynamic-se_node_acl-double-free-oops.patch @@ -0,0 +1,177 @@ +From 01d4d673558985d9a118e1e05026633c3e2ade9b Mon Sep 17 00:00:00 2001 +From: Nicholas Bellinger +Date: Wed, 7 Dec 2016 12:55:54 -0800 +Subject: target: Fix multi-session dynamic se_node_acl double free OOPs + +From: Nicholas Bellinger + +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 +Tested-by: Rob Millner +Cc: Rob Millner +Signed-off-by: Nicholas Bellinger +Signed-off-by: Greg Kroah-Hartman + + +--- + drivers/target/target_core_transport.c | 70 ++++++++++++++++++++------------- + include/target/target_core_base.h | 1 + 2 files changed, 44 insertions(+), 27 deletions(-) + +--- a/drivers/target/target_core_transport.c ++++ b/drivers/target/target_core_transport.c +@@ -423,8 +423,20 @@ static void target_complete_nacl(struct + { + struct se_node_acl *nacl = container_of(kref, + struct se_node_acl, acl_kref); ++ struct se_portal_group *se_tpg = nacl->se_tpg; + +- complete(&nacl->acl_free_comp); ++ if (!nacl->dynamic_stop) { ++ complete(&nacl->acl_free_comp); ++ return; ++ } ++ ++ mutex_lock(&se_tpg->acl_node_mutex); ++ list_del(&nacl->acl_list); ++ mutex_unlock(&se_tpg->acl_node_mutex); ++ ++ core_tpg_wait_for_nacl_pr_ref(nacl); ++ core_free_device_list_for_node(nacl, se_tpg); ++ kfree(nacl); + } + + void target_put_nacl(struct se_node_acl *nacl) +@@ -465,12 +477,39 @@ EXPORT_SYMBOL(transport_deregister_sessi + void transport_free_session(struct se_session *se_sess) + { + struct se_node_acl *se_nacl = se_sess->se_node_acl; ++ + /* + * Drop the se_node_acl->nacl_kref obtained from within + * core_tpg_get_initiator_node_acl(). + */ + if (se_nacl) { ++ struct se_portal_group *se_tpg = se_nacl->se_tpg; ++ const struct target_core_fabric_ops *se_tfo = se_tpg->se_tpg_tfo; ++ unsigned long flags; ++ + se_sess->se_node_acl = NULL; ++ ++ /* ++ * Also determine if we need to drop the extra ->cmd_kref if ++ * it had been previously dynamically generated, and ++ * the endpoint is not caching dynamic ACLs. ++ */ ++ mutex_lock(&se_tpg->acl_node_mutex); ++ if (se_nacl->dynamic_node_acl && ++ !se_tfo->tpg_check_demo_mode_cache(se_tpg)) { ++ spin_lock_irqsave(&se_nacl->nacl_sess_lock, flags); ++ if (list_empty(&se_nacl->acl_sess_list)) ++ se_nacl->dynamic_stop = true; ++ spin_unlock_irqrestore(&se_nacl->nacl_sess_lock, flags); ++ ++ if (se_nacl->dynamic_stop) ++ list_del(&se_nacl->acl_list); ++ } ++ mutex_unlock(&se_tpg->acl_node_mutex); ++ ++ if (se_nacl->dynamic_stop) ++ target_put_nacl(se_nacl); ++ + target_put_nacl(se_nacl); + } + if (se_sess->sess_cmd_map) { +@@ -484,16 +523,12 @@ EXPORT_SYMBOL(transport_free_session); + void transport_deregister_session(struct se_session *se_sess) + { + struct se_portal_group *se_tpg = se_sess->se_tpg; +- const struct target_core_fabric_ops *se_tfo; +- struct se_node_acl *se_nacl; + unsigned long flags; +- bool drop_nacl = false; + + if (!se_tpg) { + transport_free_session(se_sess); + return; + } +- se_tfo = se_tpg->se_tpg_tfo; + + spin_lock_irqsave(&se_tpg->session_lock, flags); + list_del(&se_sess->sess_list); +@@ -501,34 +536,15 @@ void transport_deregister_session(struct + se_sess->fabric_sess_ptr = NULL; + spin_unlock_irqrestore(&se_tpg->session_lock, flags); + +- /* +- * Determine if we need to do extra work for this initiator node's +- * struct se_node_acl if it had been previously dynamically generated. +- */ +- se_nacl = se_sess->se_node_acl; +- +- mutex_lock(&se_tpg->acl_node_mutex); +- if (se_nacl && se_nacl->dynamic_node_acl) { +- if (!se_tfo->tpg_check_demo_mode_cache(se_tpg)) { +- list_del(&se_nacl->acl_list); +- se_tpg->num_node_acls--; +- drop_nacl = true; +- } +- } +- mutex_unlock(&se_tpg->acl_node_mutex); +- +- if (drop_nacl) { +- core_tpg_wait_for_nacl_pr_ref(se_nacl); +- core_free_device_list_for_node(se_nacl, se_tpg); +- se_sess->se_node_acl = NULL; +- kfree(se_nacl); +- } + pr_debug("TARGET_CORE[%s]: Deregistered fabric_sess\n", + se_tpg->se_tpg_tfo->get_fabric_name()); + /* + * If last kref is dropping now for an explicit NodeACL, awake sleeping + * ->acl_free_comp caller to wakeup configfs se_node_acl->acl_group + * removal context from within transport_free_session() code. ++ * ++ * For dynamic ACL, target_put_nacl() uses target_complete_nacl() ++ * to release all remaining generate_node_acl=1 created ACL resources. + */ + + transport_free_session(se_sess); +--- a/include/target/target_core_base.h ++++ b/include/target/target_core_base.h +@@ -544,6 +544,7 @@ struct se_node_acl { + /* Used to signal demo mode created ACL, disabled by default */ + bool dynamic_node_acl; + bool acl_stop:1; ++ bool dynamic_stop; + u32 queue_depth; + u32 acl_index; + enum target_prot_type saved_prot_type; diff --git a/queue-4.4/target-obtain-se_node_acl-acl_kref-during-get_initiator_node_acl.patch b/queue-4.4/target-obtain-se_node_acl-acl_kref-during-get_initiator_node_acl.patch new file mode 100644 index 00000000000..11b5a2cfa9e --- /dev/null +++ b/queue-4.4/target-obtain-se_node_acl-acl_kref-during-get_initiator_node_acl.patch @@ -0,0 +1,200 @@ +From 21aaa23b0ebbd19334fa461370c03cbb076b3295 Mon Sep 17 00:00:00 2001 +From: Nicholas Bellinger +Date: Thu, 7 Jan 2016 22:09:27 -0800 +Subject: target: Obtain se_node_acl->acl_kref during get_initiator_node_acl + +From: Nicholas Bellinger + +commit 21aaa23b0ebbd19334fa461370c03cbb076b3295 upstream. + +This patch addresses a long standing race where obtaining +se_node_acl->acl_kref in __transport_register_session() +happens a bit too late, and leaves open the potential +for core_tpg_del_initiator_node_acl() to hit a NULL +pointer dereference. + +Instead, take ->acl_kref in core_tpg_get_initiator_node_acl() +while se_portal_group->acl_node_mutex is held, and move the +final target_put_nacl() from transport_deregister_session() +into transport_free_session() so that fabric driver login +failure handling using the modern method to still work +as expected. + +Also, update core_tpg_get_initiator_node_acl() to take +an extra reference for dynamically generated acls for +demo-mode, before returning to fabric caller. Also +update iscsi-target sendtargets special case handling +to use target_tpg_has_node_acl() when checking if +demo_mode_discovery == true during discovery lookup. + +Note the existing wait_for_completion(&acl->acl_free_comp) +in core_tpg_del_initiator_node_acl() does not change. + +Cc: Sagi Grimberg +Cc: Christoph Hellwig +Cc: Hannes Reinecke +Cc: Andy Grover +Cc: Mike Christie +Signed-off-by: Nicholas Bellinger +Signed-off-by: Greg Kroah-Hartman + +--- + drivers/target/iscsi/iscsi_target.c | 2 - + drivers/target/target_core_tpg.c | 42 ++++++++++++++++++++++++++++++++- + drivers/target/target_core_transport.c | 19 ++++++++++---- + include/target/target_core_fabric.h | 2 + + 4 files changed, 57 insertions(+), 8 deletions(-) + +--- a/drivers/target/iscsi/iscsi_target.c ++++ b/drivers/target/iscsi/iscsi_target.c +@@ -3436,7 +3436,7 @@ iscsit_build_sendtargets_response(struct + + if ((tpg->tpg_attrib.generate_node_acls == 0) && + (tpg->tpg_attrib.demo_mode_discovery == 0) && +- (!core_tpg_get_initiator_node_acl(&tpg->tpg_se_tpg, ++ (!target_tpg_has_node_acl(&tpg->tpg_se_tpg, + cmd->conn->sess->sess_ops->InitiatorName))) { + continue; + } +--- a/drivers/target/target_core_tpg.c ++++ b/drivers/target/target_core_tpg.c +@@ -75,9 +75,21 @@ struct se_node_acl *core_tpg_get_initiat + unsigned char *initiatorname) + { + struct se_node_acl *acl; +- ++ /* ++ * Obtain se_node_acl->acl_kref using fabric driver provided ++ * initiatorname[] during node acl endpoint lookup driven by ++ * new se_session login. ++ * ++ * The reference is held until se_session shutdown -> release ++ * occurs via fabric driver invoked transport_deregister_session() ++ * or transport_free_session() code. ++ */ + mutex_lock(&tpg->acl_node_mutex); + acl = __core_tpg_get_initiator_node_acl(tpg, initiatorname); ++ if (acl) { ++ if (!kref_get_unless_zero(&acl->acl_kref)) ++ acl = NULL; ++ } + mutex_unlock(&tpg->acl_node_mutex); + + return acl; +@@ -232,6 +244,25 @@ static void target_add_node_acl(struct s + acl->initiatorname); + } + ++bool target_tpg_has_node_acl(struct se_portal_group *tpg, ++ const char *initiatorname) ++{ ++ struct se_node_acl *acl; ++ bool found = false; ++ ++ mutex_lock(&tpg->acl_node_mutex); ++ list_for_each_entry(acl, &tpg->acl_node_list, acl_list) { ++ if (!strcmp(acl->initiatorname, initiatorname)) { ++ found = true; ++ break; ++ } ++ } ++ mutex_unlock(&tpg->acl_node_mutex); ++ ++ return found; ++} ++EXPORT_SYMBOL(target_tpg_has_node_acl); ++ + struct se_node_acl *core_tpg_check_initiator_node_acl( + struct se_portal_group *tpg, + unsigned char *initiatorname) +@@ -248,6 +279,15 @@ struct se_node_acl *core_tpg_check_initi + acl = target_alloc_node_acl(tpg, initiatorname); + if (!acl) + return NULL; ++ /* ++ * When allocating a dynamically generated node_acl, go ahead ++ * and take the extra kref now before returning to the fabric ++ * driver caller. ++ * ++ * Note this reference will be released at session shutdown ++ * time within transport_free_session() code. ++ */ ++ kref_get(&acl->acl_kref); + acl->dynamic_node_acl = 1; + + /* +--- a/drivers/target/target_core_transport.c ++++ b/drivers/target/target_core_transport.c +@@ -341,7 +341,6 @@ void __transport_register_session( + &buf[0], PR_REG_ISID_LEN); + se_sess->sess_bin_isid = get_unaligned_be64(&buf[0]); + } +- kref_get(&se_nacl->acl_kref); + + spin_lock_irq(&se_nacl->nacl_sess_lock); + /* +@@ -432,6 +431,7 @@ void target_put_nacl(struct se_node_acl + { + kref_put(&nacl->acl_kref, target_complete_nacl); + } ++EXPORT_SYMBOL(target_put_nacl); + + void transport_deregister_session_configfs(struct se_session *se_sess) + { +@@ -464,6 +464,15 @@ EXPORT_SYMBOL(transport_deregister_sessi + + void transport_free_session(struct se_session *se_sess) + { ++ struct se_node_acl *se_nacl = se_sess->se_node_acl; ++ /* ++ * Drop the se_node_acl->nacl_kref obtained from within ++ * core_tpg_get_initiator_node_acl(). ++ */ ++ if (se_nacl) { ++ se_sess->se_node_acl = NULL; ++ target_put_nacl(se_nacl); ++ } + if (se_sess->sess_cmd_map) { + percpu_ida_destroy(&se_sess->sess_tag_pool); + kvfree(se_sess->sess_cmd_map); +@@ -478,7 +487,7 @@ void transport_deregister_session(struct + const struct target_core_fabric_ops *se_tfo; + struct se_node_acl *se_nacl; + unsigned long flags; +- bool comp_nacl = true, drop_nacl = false; ++ bool drop_nacl = false; + + if (!se_tpg) { + transport_free_session(se_sess); +@@ -511,18 +520,16 @@ void transport_deregister_session(struct + if (drop_nacl) { + core_tpg_wait_for_nacl_pr_ref(se_nacl); + core_free_device_list_for_node(se_nacl, se_tpg); ++ se_sess->se_node_acl = NULL; + kfree(se_nacl); +- comp_nacl = false; + } + pr_debug("TARGET_CORE[%s]: Deregistered fabric_sess\n", + se_tpg->se_tpg_tfo->get_fabric_name()); + /* + * If last kref is dropping now for an explicit NodeACL, awake sleeping + * ->acl_free_comp caller to wakeup configfs se_node_acl->acl_group +- * removal context. ++ * removal context from within transport_free_session() code. + */ +- if (se_nacl && comp_nacl) +- target_put_nacl(se_nacl); + + transport_free_session(se_sess); + } +--- a/include/target/target_core_fabric.h ++++ b/include/target/target_core_fabric.h +@@ -168,6 +168,8 @@ void core_allocate_nexus_loss_ua(struct + + struct se_node_acl *core_tpg_get_initiator_node_acl(struct se_portal_group *tpg, + unsigned char *); ++bool target_tpg_has_node_acl(struct se_portal_group *tpg, ++ const char *); + struct se_node_acl *core_tpg_check_initiator_node_acl(struct se_portal_group *, + unsigned char *); + int core_tpg_set_initiator_node_queue_depth(struct se_portal_group *,