scsi: target: tcmu: avoid use-after-free after command timeout [Linux 4.19.72]

This Linux kernel change "scsi: target: tcmu: avoid use-after-free after command timeout" is included in the Linux 4.19.72 release. This change is authored by Dmitry Fomichev <dmitry.fomichev [at] wdc.com> on Sun Aug 11 11:25:10 2019 -0700. The commit for this change in Linux stable tree is b8cd0b7 (patch) which is from upstream commit a86a758. The same Linux upstream change may have been applied to various maintained Linux releases and you can find all Linux releases containing changes from upstream a86a758.

scsi: target: tcmu: avoid use-after-free after command timeout

[ Upstream commit a86a75865ff4d8c05f355d1750a5250aec89ab15 ]

In tcmu_handle_completion() function, the variable called read_len is
always initialized with a value taken from se_cmd structure. If this
function is called to complete an expired (timed out) out command, the
session command pointed by se_cmd is likely to be already deallocated by
the target core at that moment. As the result, this access triggers a
use-after-free warning from KASAN.

This patch fixes the code not to touch se_cmd when completing timed out
TCMU commands. It also resets the pointer to se_cmd at the time when the
TCMU_CMD_BIT_EXPIRED flag is set because it is going to become invalid
after calling target_complete_cmd() later in the same function,
tcmu_check_expired_cmd().

Signed-off-by: Dmitry Fomichev <dmitry.fomichev@wdc.com>
Acked-by: Mike Christie <mchristi@redhat.com>
Reviewed-by: Damien Le Moal <damien.lemoal@wdc.com>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>

There are 9 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 drivers/target/target_core_user.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/drivers/target/target_core_user.c b/drivers/target/target_core_user.c
index c46efa4..7159e83 100644
--- a/drivers/target/target_core_user.c
+++ b/drivers/target/target_core_user.c
@@ -1143,14 +1143,16 @@ static void tcmu_handle_completion(struct tcmu_cmd *cmd, struct tcmu_cmd_entry *
    struct se_cmd *se_cmd = cmd->se_cmd;
    struct tcmu_dev *udev = cmd->tcmu_dev;
    bool read_len_valid = false;
-   uint32_t read_len = se_cmd->data_length;
+   uint32_t read_len;

    /*
     * cmd has been completed already from timeout, just reclaim
     * data area space and free cmd
     */
-   if (test_bit(TCMU_CMD_BIT_EXPIRED, &cmd->flags))
+   if (test_bit(TCMU_CMD_BIT_EXPIRED, &cmd->flags)) {
+       WARN_ON_ONCE(se_cmd);
        goto out;
+   }

    list_del_init(&cmd->queue_entry);

@@ -1163,6 +1165,7 @@ static void tcmu_handle_completion(struct tcmu_cmd *cmd, struct tcmu_cmd_entry *
        goto done;
    }

+   read_len = se_cmd->data_length;
    if (se_cmd->data_direction == DMA_FROM_DEVICE &&
        (entry->hdr.uflags & TCMU_UFLAG_READ_LEN) && entry->rsp.read_len) {
        read_len_valid = true;
@@ -1318,6 +1321,7 @@ static int tcmu_check_expired_cmd(int id, void *p, void *data)
         */
        scsi_status = SAM_STAT_CHECK_CONDITION;
        list_del_init(&cmd->queue_entry);
+       cmd->se_cmd = NULL;
    } else {
        list_del_init(&cmd->queue_entry);
        idr_remove(&udev->commands, id);
@@ -2036,6 +2040,7 @@ static void tcmu_reset_ring(struct tcmu_dev *udev, u8 err_level)

        idr_remove(&udev->commands, i);
        if (!test_bit(TCMU_CMD_BIT_EXPIRED, &cmd->flags)) {
+           WARN_ON(!cmd->se_cmd);
            list_del_init(&cmd->queue_entry);
            if (err_level == 1) {
                /*

Leave a Reply

Your email address will not be published. Required fields are marked *