aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoland Dreier <roland@purestorage.com>2012-07-16 14:04:40 -0400
committerNicholas Bellinger <nab@linux-iscsi.org>2012-07-16 20:35:26 -0400
commitbc187ea6c3b3d20bd190f3ee90c954aee0ce8aad (patch)
treea7e9aaf79297e966773504f8df06451808c3df9c
parent7c78b8de267cc4bc63c4f95bda694be606c3644d (diff)
target: Check sess_tearing_down in target_get_sess_cmd()
Target core code assumes that target_splice_sess_cmd_list() has set sess_tearing_down and moved the list of pending commands to sess_wait_list, no more commands will be added to the session; if any are added, nothing keeps the se_session from being freed while the command is still in flight, which e.g. leads to use-after-free of se_cmd->se_sess in target_release_cmd_kref(). To enforce this invariant, put a check of sess_tearing_down inside of sess_cmd_lock in target_get_sess_cmd(); any checks before this are racy and can lead to the use-after-free described above. For example, the qla_target check in qlt_do_work() checks sess_tearing_down from work thread context but then drops all locks before calling target_submit_cmd() (as it must, since that is a sleeping function). However, since no locks are held, anything can happen with respect to the session it has looked up -- although it does correctly get sess_kref within its lock, so the memory won't be freed while target_submit_cmd() is actually running, nothing stops eg an ACL from being dropped and calling ->shutdown_session() (which calls into target_splice_sess_cmd_list()) before we get to target_get_sess_cmd(). Once this happens, the se_session memory can be freed as soon as target_submit_cmd() returns and qlt_do_work() drops its reference, even though we've just added a command to sess_cmd_list. To prevent this use-after-free, check sess_tearing_down inside of sess_cmd_lock right before target_get_sess_cmd() adds a command to sess_cmd_list; this is synchronized with target_splice_sess_cmd_list() so that every command is either waited for or not added to the queue. (nab: Keep target_submit_cmd() returning void for now..) Signed-off-by: Roland Dreier <roland@purestorage.com> Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org>
-rw-r--r--drivers/target/target_core_transport.c24
1 files changed, 19 insertions, 5 deletions
diff --git a/drivers/target/target_core_transport.c b/drivers/target/target_core_transport.c
index 7b8543480e5f..a979514061e7 100644
--- a/drivers/target/target_core_transport.c
+++ b/drivers/target/target_core_transport.c
@@ -70,7 +70,7 @@ static void transport_complete_task_attr(struct se_cmd *cmd);
70static void transport_handle_queue_full(struct se_cmd *cmd, 70static void transport_handle_queue_full(struct se_cmd *cmd,
71 struct se_device *dev); 71 struct se_device *dev);
72static int transport_generic_get_mem(struct se_cmd *cmd); 72static int transport_generic_get_mem(struct se_cmd *cmd);
73static void target_get_sess_cmd(struct se_session *, struct se_cmd *, bool); 73static int target_get_sess_cmd(struct se_session *, struct se_cmd *, bool);
74static void transport_put_cmd(struct se_cmd *cmd); 74static void transport_put_cmd(struct se_cmd *cmd);
75static int transport_set_sense_codes(struct se_cmd *cmd, u8 asc, u8 ascq); 75static int transport_set_sense_codes(struct se_cmd *cmd, u8 asc, u8 ascq);
76static void target_complete_ok_work(struct work_struct *work); 76static void target_complete_ok_work(struct work_struct *work);
@@ -1477,7 +1477,9 @@ void target_submit_cmd(struct se_cmd *se_cmd, struct se_session *se_sess,
1477 * for fabrics using TARGET_SCF_ACK_KREF that expect a second 1477 * for fabrics using TARGET_SCF_ACK_KREF that expect a second
1478 * kref_put() to happen during fabric packet acknowledgement. 1478 * kref_put() to happen during fabric packet acknowledgement.
1479 */ 1479 */
1480 target_get_sess_cmd(se_sess, se_cmd, (flags & TARGET_SCF_ACK_KREF)); 1480 rc = target_get_sess_cmd(se_sess, se_cmd, (flags & TARGET_SCF_ACK_KREF));
1481 if (rc)
1482 return;
1481 /* 1483 /*
1482 * Signal bidirectional data payloads to target-core 1484 * Signal bidirectional data payloads to target-core
1483 */ 1485 */
@@ -1561,7 +1563,11 @@ int target_submit_tmr(struct se_cmd *se_cmd, struct se_session *se_sess,
1561 se_cmd->se_tmr_req->ref_task_tag = tag; 1563 se_cmd->se_tmr_req->ref_task_tag = tag;
1562 1564
1563 /* See target_submit_cmd for commentary */ 1565 /* See target_submit_cmd for commentary */
1564 target_get_sess_cmd(se_sess, se_cmd, (flags & TARGET_SCF_ACK_KREF)); 1566 ret = target_get_sess_cmd(se_sess, se_cmd, (flags & TARGET_SCF_ACK_KREF));
1567 if (ret) {
1568 core_tmr_release_req(se_cmd->se_tmr_req);
1569 return ret;
1570 }
1565 1571
1566 ret = transport_lookup_tmr_lun(se_cmd, unpacked_lun); 1572 ret = transport_lookup_tmr_lun(se_cmd, unpacked_lun);
1567 if (ret) { 1573 if (ret) {
@@ -2409,10 +2415,11 @@ EXPORT_SYMBOL(transport_generic_free_cmd);
2409 * @se_cmd: command descriptor to add 2415 * @se_cmd: command descriptor to add
2410 * @ack_kref: Signal that fabric will perform an ack target_put_sess_cmd() 2416 * @ack_kref: Signal that fabric will perform an ack target_put_sess_cmd()
2411 */ 2417 */
2412static void target_get_sess_cmd(struct se_session *se_sess, struct se_cmd *se_cmd, 2418static int target_get_sess_cmd(struct se_session *se_sess, struct se_cmd *se_cmd,
2413 bool ack_kref) 2419 bool ack_kref)
2414{ 2420{
2415 unsigned long flags; 2421 unsigned long flags;
2422 int ret = 0;
2416 2423
2417 kref_init(&se_cmd->cmd_kref); 2424 kref_init(&se_cmd->cmd_kref);
2418 /* 2425 /*
@@ -2426,9 +2433,16 @@ static void target_get_sess_cmd(struct se_session *se_sess, struct se_cmd *se_cm
2426 } 2433 }
2427 2434
2428 spin_lock_irqsave(&se_sess->sess_cmd_lock, flags); 2435 spin_lock_irqsave(&se_sess->sess_cmd_lock, flags);
2436 if (se_sess->sess_tearing_down) {
2437 ret = -ESHUTDOWN;
2438 goto out;
2439 }
2429 list_add_tail(&se_cmd->se_cmd_list, &se_sess->sess_cmd_list); 2440 list_add_tail(&se_cmd->se_cmd_list, &se_sess->sess_cmd_list);
2430 se_cmd->check_release = 1; 2441 se_cmd->check_release = 1;
2442
2443out:
2431 spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); 2444 spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags);
2445 return ret;
2432} 2446}
2433 2447
2434static void target_release_cmd_kref(struct kref *kref) 2448static void target_release_cmd_kref(struct kref *kref)