From 54b3dcaa30fb340b91fe04752dc273b68e3d2ead Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Tue, 18 Aug 2020 15:44:10 +0200 Subject: [PATCH] test-tool: add LogoutDuringIOAsync test case This attempts to reproduce upstream LIO reports of a use after free bug when logout occurs alongside concurrent I/O. Signed-off-by: David Disseldorp --- test-tool/iscsi-test-cu.c | 1 + test-tool/iscsi-test-cu.h | 1 + test-tool/test_async_write.c | 107 ++++++++++++++++++++++++++++++++++- 3 files changed, 107 insertions(+), 2 deletions(-) diff --git a/test-tool/iscsi-test-cu.c b/test-tool/iscsi-test-cu.c index 31ab549..56575a5 100644 --- a/test-tool/iscsi-test-cu.c +++ b/test-tool/iscsi-test-cu.c @@ -593,6 +593,7 @@ static CU_TestInfo tests_iscsi_residuals[] = { static CU_TestInfo tests_iscsi_tmf[] = { { "AbortTaskSimpleAsync", test_async_abort_simple }, { "LUNResetSimpleAsync", test_async_lu_reset_simple }, + { "LogoutDuringIOAsync", test_async_io_logout }, CU_TEST_INFO_NULL }; diff --git a/test-tool/iscsi-test-cu.h b/test-tool/iscsi-test-cu.h index 601297d..af5cced 100644 --- a/test-tool/iscsi-test-cu.h +++ b/test-tool/iscsi-test-cu.h @@ -258,6 +258,7 @@ void test_write10_wrprotect(void); void test_write10_dpofua(void); void test_write10_residuals(void); void test_async_write(void); +void test_async_io_logout(void); void test_write12_simple(void); void test_write12_beyond_eol(void); diff --git a/test-tool/test_async_write.c b/test-tool/test_async_write.c index f7c534f..cf5efcd 100644 --- a/test-tool/test_async_write.c +++ b/test-tool/test_async_write.c @@ -1,5 +1,5 @@ /* - Copyright (C) SUSE LINUX GmbH 2016 + Copyright (C) SUSE LLC 2016-2020 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -30,6 +30,8 @@ struct tests_async_write_state { uint32_t io_dispatched; uint32_t io_completed; uint32_t prev_cmdsn; + uint32_t logout_sent; + uint32_t logout_cmpl; }; static void @@ -39,6 +41,12 @@ test_async_write_cb(struct iscsi_context *iscsi __attribute__((unused)), struct scsi_task *atask = command_data; struct tests_async_write_state *state = private_data; + if (state->logout_cmpl) { + CU_ASSERT_EQUAL(status, SCSI_STATUS_CANCELLED); + logging(LOG_VERBOSE, "WRITE10 cancelled after logout"); + return; + } + state->io_completed++; logging(LOG_VERBOSE, "WRITE10 completed: %d of %d (CmdSN=%d)", state->io_completed, state->io_dispatched, atask->cmdsn); @@ -59,7 +67,7 @@ void test_async_write(void) { int i, ret; - struct tests_async_write_state state = { 0, 0, 0 }; + struct tests_async_write_state state = { }; int blocks_per_io = 8; int num_ios = 1000; /* IOs in flight concurrently, but all using the same src buffer */ @@ -118,3 +126,98 @@ test_async_write(void) free(buf); } + +static void +test_async_io_logout_cb(struct iscsi_context *iscsi __attribute__((unused)), + int status, void *command_data __attribute__((unused)), + void *private_data) +{ + struct tests_async_write_state *state = private_data; + + state->logout_cmpl++; + logging(LOG_VERBOSE, "Logout completed with %d IOs outstanding", + state->io_dispatched - state->io_completed); + CU_ASSERT_EQUAL(status, SCSI_STATUS_GOOD); +} + +void +test_async_io_logout(void) +{ + int i, ret; + struct tests_async_write_state state = { }; + int blocks_per_io = 8; + int num_ios = 10; + /* IOs in flight concurrently, but all using the same src buffer */ + unsigned char *buf; + + CHECK_FOR_DATALOSS; + CHECK_FOR_SBC; + CHECK_FOR_ISCSI(sd); + + if (maximum_transfer_length + && maximum_transfer_length < (blocks_per_io * num_ios)) { + CU_PASS("[SKIPPED] device too small for async IO test"); + return; + } + + buf = calloc(block_size, blocks_per_io); + CU_ASSERT(buf != NULL); + if (!buf) + return; + + iscsi_set_noautoreconnect(sd->iscsi_ctx, 1); + + for (i = 0; i < num_ios; i++) { + uint32_t lba = i * blocks_per_io; + struct scsi_task *atask; + + atask = scsi_cdb_write10(lba, blocks_per_io * block_size, + block_size, 0, 0, 0, 0, 0); + CU_ASSERT_PTR_NOT_NULL_FATAL(atask); + + ret = scsi_task_add_data_out_buffer(atask, + blocks_per_io * block_size, + buf); + CU_ASSERT_EQUAL(ret, 0); + + ret = iscsi_scsi_command_async(sd->iscsi_ctx, sd->iscsi_lun, + atask, test_async_write_cb, NULL, + &state); + CU_ASSERT_EQUAL(ret, 0); + + state.io_dispatched++; + logging(LOG_VERBOSE, "WRITE10 dispatched: %d of %d (cmdsn=%d)", + state.io_dispatched, num_ios, atask->cmdsn); + } + + while (!state.logout_cmpl) { + struct pollfd pfd; + + pfd.fd = iscsi_get_fd(sd->iscsi_ctx); + pfd.events = iscsi_which_events(sd->iscsi_ctx); + + ret = poll(&pfd, 1, -1); + CU_ASSERT_NOT_EQUAL(ret, -1); + + ret = iscsi_service(sd->iscsi_ctx, pfd.revents); + CU_ASSERT_EQUAL(ret, 0); + + /* attempt logout after one of the dispatch IOs has completed */ + if (!state.logout_sent && state.io_completed > 0) { + ret = iscsi_logout_async(sd->iscsi_ctx, + test_async_io_logout_cb, + &state); + CU_ASSERT_EQUAL(ret, 0); + + state.logout_sent++; + logging(LOG_VERBOSE, + "Logout dispatched following %d IO completions", + state.io_completed); + } + } + + iscsi_destroy_context(sd->iscsi_ctx); + sd->iscsi_ctx = iscsi_context_login(initiatorname1, sd->iscsi_url, &sd->iscsi_lun); + CU_ASSERT_PTR_NOT_NULL(sd->iscsi_ctx); + free(buf); +}