Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

scsi: target/core: Make sure that target_wait_for_sess_cmds() waits long enough

A session must only be released after all code that accesses the session
structure has finished. Make sure that this is the case by introducing a
new command counter per session that is only decremented after the
.release_cmd() callback has finished. This patch fixes the following crash:

BUG: KASAN: use-after-free in do_raw_spin_lock+0x1c/0x130
Read of size 4 at addr ffff8801534b16e4 by task rmdir/14805
CPU: 16 PID: 14805 Comm: rmdir Not tainted 4.18.0-rc2-dbg+ #5
Call Trace:
dump_stack+0xa4/0xf5
print_address_description+0x6f/0x270
kasan_report+0x241/0x360
__asan_load4+0x78/0x80
do_raw_spin_lock+0x1c/0x130
_raw_spin_lock_irqsave+0x52/0x60
srpt_set_ch_state+0x27/0x70 [ib_srpt]
srpt_disconnect_ch+0x1b/0xc0 [ib_srpt]
srpt_close_session+0xa8/0x260 [ib_srpt]
target_shutdown_sessions+0x170/0x180 [target_core_mod]
core_tpg_del_initiator_node_acl+0xf3/0x200 [target_core_mod]
target_fabric_nacl_base_release+0x25/0x30 [target_core_mod]
config_item_release+0x9c/0x110 [configfs]
config_item_put+0x26/0x30 [configfs]
configfs_rmdir+0x3b8/0x510 [configfs]
vfs_rmdir+0xb3/0x1e0
do_rmdir+0x262/0x2c0
do_syscall_64+0x77/0x230
entry_SYSCALL_64_after_hwframe+0x49/0xbe

Cc: Nicholas Bellinger <nab@linux-iscsi.org>
Cc: Mike Christie <mchristi@redhat.com>
Cc: Christoph Hellwig <hch@lst.de>
Cc: David Disseldorp <ddiss@suse.de>
Cc: Hannes Reinecke <hare@suse.de>
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>

authored by

Bart Van Assche and committed by
Martin K. Petersen
ad669505 a95be384

+32 -12
+25 -10
drivers/target/target_core_transport.c
··· 224 224 sub_api_initialized = 1; 225 225 } 226 226 227 + static void target_release_sess_cmd_refcnt(struct percpu_ref *ref) 228 + { 229 + struct se_session *sess = container_of(ref, typeof(*sess), cmd_count); 230 + 231 + wake_up(&sess->cmd_list_wq); 232 + } 233 + 227 234 /** 228 235 * transport_init_session - initialize a session object 229 236 * @se_sess: Session object pointer. 230 237 * 231 238 * The caller must have zero-initialized @se_sess before calling this function. 232 239 */ 233 - void transport_init_session(struct se_session *se_sess) 240 + int transport_init_session(struct se_session *se_sess) 234 241 { 235 242 INIT_LIST_HEAD(&se_sess->sess_list); 236 243 INIT_LIST_HEAD(&se_sess->sess_acl_list); 237 244 INIT_LIST_HEAD(&se_sess->sess_cmd_list); 238 245 spin_lock_init(&se_sess->sess_cmd_lock); 239 246 init_waitqueue_head(&se_sess->cmd_list_wq); 247 + return percpu_ref_init(&se_sess->cmd_count, 248 + target_release_sess_cmd_refcnt, 0, GFP_KERNEL); 240 249 } 241 250 EXPORT_SYMBOL(transport_init_session); 242 251 ··· 256 247 struct se_session *transport_alloc_session(enum target_prot_op sup_prot_ops) 257 248 { 258 249 struct se_session *se_sess; 250 + int ret; 259 251 260 252 se_sess = kmem_cache_zalloc(se_sess_cache, GFP_KERNEL); 261 253 if (!se_sess) { ··· 264 254 " se_sess_cache\n"); 265 255 return ERR_PTR(-ENOMEM); 266 256 } 267 - transport_init_session(se_sess); 257 + ret = transport_init_session(se_sess); 258 + if (ret < 0) { 259 + kfree(se_sess); 260 + return ERR_PTR(ret); 261 + } 268 262 se_sess->sup_prot_ops = sup_prot_ops; 269 263 270 264 return se_sess; ··· 592 578 sbitmap_queue_free(&se_sess->sess_tag_pool); 593 579 kvfree(se_sess->sess_cmd_map); 594 580 } 581 + percpu_ref_exit(&se_sess->cmd_count); 595 582 kmem_cache_free(se_sess_cache, se_sess); 596 583 } 597 584 EXPORT_SYMBOL(transport_free_session); ··· 2731 2716 } 2732 2717 se_cmd->transport_state |= CMD_T_PRE_EXECUTE; 2733 2718 list_add_tail(&se_cmd->se_cmd_list, &se_sess->sess_cmd_list); 2719 + percpu_ref_get(&se_sess->cmd_count); 2734 2720 out: 2735 2721 spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); 2736 2722 ··· 2762 2746 if (se_sess) { 2763 2747 spin_lock_irqsave(&se_sess->sess_cmd_lock, flags); 2764 2748 list_del_init(&se_cmd->se_cmd_list); 2765 - if (se_sess->sess_tearing_down && list_empty(&se_sess->sess_cmd_list)) 2766 - wake_up(&se_sess->cmd_list_wq); 2767 2749 spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); 2768 2750 } 2769 2751 ··· 2769 2755 se_cmd->se_tfo->release_cmd(se_cmd); 2770 2756 if (compl) 2771 2757 complete(compl); 2758 + 2759 + percpu_ref_put(&se_sess->cmd_count); 2772 2760 } 2773 2761 2774 2762 /** ··· 2899 2883 spin_lock_irqsave(&se_sess->sess_cmd_lock, flags); 2900 2884 se_sess->sess_tearing_down = 1; 2901 2885 spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); 2886 + 2887 + percpu_ref_kill(&se_sess->cmd_count); 2902 2888 } 2903 2889 EXPORT_SYMBOL(target_sess_cmd_list_set_waiting); 2904 2890 ··· 2915 2897 2916 2898 WARN_ON_ONCE(!se_sess->sess_tearing_down); 2917 2899 2918 - spin_lock_irq(&se_sess->sess_cmd_lock); 2919 2900 do { 2920 - ret = wait_event_lock_irq_timeout( 2921 - se_sess->cmd_list_wq, 2922 - list_empty(&se_sess->sess_cmd_list), 2923 - se_sess->sess_cmd_lock, 180 * HZ); 2901 + ret = wait_event_timeout(se_sess->cmd_list_wq, 2902 + percpu_ref_is_zero(&se_sess->cmd_count), 2903 + 180 * HZ); 2924 2904 list_for_each_entry(cmd, &se_sess->sess_cmd_list, se_cmd_list) 2925 2905 target_show_cmd("session shutdown: still waiting for ", 2926 2906 cmd); 2927 2907 } while (ret <= 0); 2928 - spin_unlock_irq(&se_sess->sess_cmd_lock); 2929 2908 } 2930 2909 EXPORT_SYMBOL(target_wait_for_sess_cmds); 2931 2910
+5 -1
drivers/target/target_core_xcopy.c
··· 474 474 475 475 int target_xcopy_setup_pt(void) 476 476 { 477 + int ret; 478 + 477 479 xcopy_wq = alloc_workqueue("xcopy_wq", WQ_MEM_RECLAIM, 0); 478 480 if (!xcopy_wq) { 479 481 pr_err("Unable to allocate xcopy_wq\n"); ··· 493 491 INIT_LIST_HEAD(&xcopy_pt_nacl.acl_list); 494 492 INIT_LIST_HEAD(&xcopy_pt_nacl.acl_sess_list); 495 493 memset(&xcopy_pt_sess, 0, sizeof(struct se_session)); 496 - transport_init_session(&xcopy_pt_sess); 494 + ret = transport_init_session(&xcopy_pt_sess); 495 + if (ret < 0) 496 + return ret; 497 497 498 498 xcopy_pt_nacl.se_tpg = &xcopy_pt_tpg; 499 499 xcopy_pt_nacl.nacl_sess = &xcopy_pt_sess;
+1
include/target/target_core_base.h
··· 603 603 struct se_node_acl *se_node_acl; 604 604 struct se_portal_group *se_tpg; 605 605 void *fabric_sess_ptr; 606 + struct percpu_ref cmd_count; 606 607 struct list_head sess_list; 607 608 struct list_head sess_acl_list; 608 609 struct list_head sess_cmd_list;
+1 -1
include/target/target_core_fabric.h
··· 126 126 struct se_session *, void *)); 127 127 void target_remove_session(struct se_session *); 128 128 129 - void transport_init_session(struct se_session *); 129 + int transport_init_session(struct se_session *se_sess); 130 130 struct se_session *transport_alloc_session(enum target_prot_op); 131 131 int transport_alloc_session_tags(struct se_session *, unsigned int, 132 132 unsigned int);