diff options
author | Roland Dreier <roland@purestorage.com> | 2012-07-16 14:04:40 -0400 |
---|---|---|
committer | Nicholas Bellinger <nab@linux-iscsi.org> | 2012-07-16 20:35:26 -0400 |
commit | bc187ea6c3b3d20bd190f3ee90c954aee0ce8aad (patch) | |
tree | a7e9aaf79297e966773504f8df06451808c3df9c | |
parent | 7c78b8de267cc4bc63c4f95bda694be606c3644d (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.c | 24 |
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); | |||
70 | static void transport_handle_queue_full(struct se_cmd *cmd, | 70 | static void transport_handle_queue_full(struct se_cmd *cmd, |
71 | struct se_device *dev); | 71 | struct se_device *dev); |
72 | static int transport_generic_get_mem(struct se_cmd *cmd); | 72 | static int transport_generic_get_mem(struct se_cmd *cmd); |
73 | static void target_get_sess_cmd(struct se_session *, struct se_cmd *, bool); | 73 | static int target_get_sess_cmd(struct se_session *, struct se_cmd *, bool); |
74 | static void transport_put_cmd(struct se_cmd *cmd); | 74 | static void transport_put_cmd(struct se_cmd *cmd); |
75 | static int transport_set_sense_codes(struct se_cmd *cmd, u8 asc, u8 ascq); | 75 | static int transport_set_sense_codes(struct se_cmd *cmd, u8 asc, u8 ascq); |
76 | static void target_complete_ok_work(struct work_struct *work); | 76 | static 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 | */ |
2412 | static void target_get_sess_cmd(struct se_session *se_sess, struct se_cmd *se_cmd, | 2418 | static 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 | |||
2443 | out: | ||
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 | ||
2434 | static void target_release_cmd_kref(struct kref *kref) | 2448 | static void target_release_cmd_kref(struct kref *kref) |