From cc83f517ea9ae254795b8ac197f6d720d016a95a Mon Sep 17 00:00:00 2001 From: Vladislav Bolkhovitin Date: Thu, 15 Oct 2009 15:56:57 +0000 Subject: [PATCH] - Fix for a possible DoS, when misbehavine scst_user's handler hangs several memory allocations (= count of iSCSI read threads) and by so prevents other sessions from being served correctly. Spotted by Erez Zilber - iSCSI read state machine cleanups - Fix a race when just freed iSCSI session accessed, because the corresponding SCST session is still being unregistered. git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@1224 d57e44dd-8a1f-0410-8b47-8ef2f437770f --- iscsi-scst/kernel/conn.c | 3 +- iscsi-scst/kernel/iscsi.c | 224 ++++++++++------- iscsi-scst/kernel/iscsi.h | 5 +- iscsi-scst/kernel/nthread.c | 478 +++++++++++++++++++----------------- iscsi-scst/kernel/session.c | 37 ++- 5 files changed, 413 insertions(+), 334 deletions(-) diff --git a/iscsi-scst/kernel/conn.c b/iscsi-scst/kernel/conn.c index d26cc5002..21ec29c7a 100644 --- a/iscsi-scst/kernel/conn.c +++ b/iscsi-scst/kernel/conn.c @@ -128,7 +128,7 @@ struct iscsi_conn *conn_lookup(struct iscsi_session *session, u16 cid) return NULL; } -static void iscsi_make_conn_rd_active(struct iscsi_conn *conn) +void iscsi_make_conn_rd_active(struct iscsi_conn *conn) { TRACE_ENTRY(); @@ -477,6 +477,7 @@ static int iscsi_conn_alloc(struct iscsi_session *session, INIT_LIST_HEAD(&conn->write_list); INIT_LIST_HEAD(&conn->written_list); setup_timer(&conn->rsp_timer, conn_rsp_timer_fn, (unsigned long)conn); + init_waitqueue_head(&conn->read_state_waitQ); init_completion(&conn->ready_to_free); INIT_LIST_HEAD(&conn->reinst_pending_cmd_list); diff --git a/iscsi-scst/kernel/iscsi.c b/iscsi-scst/kernel/iscsi.c index cce47e5c8..ff6b17c54 100644 --- a/iscsi-scst/kernel/iscsi.c +++ b/iscsi-scst/kernel/iscsi.c @@ -200,7 +200,6 @@ struct iscsi_cmnd *cmnd_alloc(struct iscsi_conn *conn, cmnd->scst_state = ISCSI_CMD_STATE_NEW; cmnd->conn = conn; cmnd->parent_req = parent; - init_waitqueue_head(&cmnd->scst_waitQ); if (parent == NULL) { conn_get(conn); @@ -1368,6 +1367,108 @@ static inline u32 get_next_ttt(struct iscsi_conn *conn) return cpu_to_be32(ttt); } +int cmnd_rx_continue(struct iscsi_cmnd *req) +{ + struct iscsi_conn *conn = req->conn; + struct iscsi_session *session = conn->session; + struct iscsi_scsi_cmd_hdr *req_hdr = cmnd_hdr(req); + struct scst_cmd *scst_cmd = req->scst_cmd; + scst_data_direction dir; + int res = 0; + + TRACE_ENTRY(); + + TRACE_DBG("scsi command: %02x", req_hdr->scb[0]); + + if (unlikely(req->scst_state != ISCSI_CMD_STATE_AFTER_PREPROC)) { + TRACE_DBG("req %p is in %x state", req, req->scst_state); + if (req->scst_state == ISCSI_CMD_STATE_PROCESSED) { + cmnd_reject_scsi_cmd(req); + goto out; + } + if (unlikely(req->tm_aborted)) { + TRACE_MGMT_DBG("req %p (scst_cmd %p) aborted", req, + req->scst_cmd); + cmnd_prepare_get_rejected_cmd_data(req); + goto out; + } + sBUG(); + } + + dir = scst_cmd_get_data_direction(scst_cmd); + if (dir & SCST_DATA_WRITE) { + req->is_unsolicited_data = !(req_hdr->flags & ISCSI_CMD_FINAL); + req->r2t_length = be32_to_cpu(req_hdr->data_length) - + req->pdu.datasize; + if (req->r2t_length > 0) + req->data_waiting = 1; + } else { + if (unlikely(!(req_hdr->flags & ISCSI_CMD_FINAL) || + req->pdu.datasize)) { + PRINT_ERROR("Unexpected unsolicited data (ITT %x " + "CDB %x", cmnd_itt(req), req_hdr->scb[0]); + scst_set_cmd_error(scst_cmd, + SCST_LOAD_SENSE(iscsi_sense_unexpected_unsolicited_data)); + if (scst_cmd_get_sense_buffer(scst_cmd) != NULL) + create_status_rsp(req, SAM_STAT_CHECK_CONDITION, + scst_cmd_get_sense_buffer(scst_cmd), + scst_cmd_get_sense_buffer_len(scst_cmd)); + else + create_status_rsp(req, SAM_STAT_BUSY, NULL, 0); + cmnd_reject_scsi_cmd(req); + goto out; + } + } + + req->target_task_tag = get_next_ttt(conn); + if (dir != SCST_DATA_BIDI) { + req->sg = scst_cmd_get_sg(scst_cmd); + req->sg_cnt = scst_cmd_get_sg_cnt(scst_cmd); + req->bufflen = scst_cmd_get_bufflen(scst_cmd); + } else { + req->sg = scst_cmd_get_in_sg(scst_cmd); + req->sg_cnt = scst_cmd_get_in_sg_cnt(scst_cmd); + req->bufflen = scst_cmd_get_in_bufflen(scst_cmd); + } + if (unlikely(req->r2t_length > req->bufflen)) { + PRINT_ERROR("req->r2t_length %d > req->bufflen %d", + req->r2t_length, req->bufflen); + req->r2t_length = req->bufflen; + } + + TRACE_DBG("req=%p, dir=%d, is_unsolicited_data=%d, " + "r2t_length=%d, bufflen=%d", req, dir, + req->is_unsolicited_data, req->r2t_length, req->bufflen); + + if (unlikely(!session->sess_param.immediate_data && + req->pdu.datasize)) { + PRINT_ERROR("Initiator %s violated negotiated paremeters: " + "forbidden immediate data sent (ITT %x, op %x)", + session->initiator_name, cmnd_itt(req), + req_hdr->scb[0]); + res = -EINVAL; + goto out; + } + + if (unlikely(session->sess_param.initial_r2t && + !(req_hdr->flags & ISCSI_CMD_FINAL))) { + PRINT_ERROR("Initiator %s violated negotiated paremeters: " + "initial R2T is required (ITT %x, op %x)", + session->initiator_name, cmnd_itt(req), + req_hdr->scb[0]); + res = -EINVAL; + goto out; + } + + if (req->pdu.datasize) + res = cmnd_prepare_recv_pdu(conn, req, 0, req->pdu.datasize); + +out: + /* Aborted commands will be freed in cmnd_rx_end() */ + TRACE_EXIT_RES(res); + return res; +} + static int scsi_cmnd_start(struct iscsi_cmnd *req) { struct iscsi_conn *conn = req->conn; @@ -1492,92 +1593,17 @@ static int scsi_cmnd_start(struct iscsi_cmnd *req) TRACE_DBG("START Command (tag %d, queue_type %d)", req_hdr->itt, scst_cmd->queue_type); req->scst_state = ISCSI_CMD_STATE_RX_CMD; + conn->rx_task = current; scst_cmd_init_stage1_done(scst_cmd, SCST_CONTEXT_DIRECT, 0); - wait_event(req->scst_waitQ, req->scst_state != ISCSI_CMD_STATE_RX_CMD); - - if (unlikely(req->scst_state != ISCSI_CMD_STATE_AFTER_PREPROC)) { - TRACE_DBG("req %p is in %x state", req, req->scst_state); - if (req->scst_state == ISCSI_CMD_STATE_PROCESSED) { - cmnd_reject_scsi_cmd(req); - goto out; - } - if (unlikely(req->tm_aborted)) { - TRACE_MGMT_DBG("req %p (scst_cmd %p) aborted", req, - req->scst_cmd); - cmnd_prepare_get_rejected_cmd_data(req); - goto out; - } - sBUG(); + if (req->scst_state != ISCSI_CMD_STATE_RX_CMD) + res = cmnd_rx_continue(req); + else { + TRACE_DBG("Delaying req %p post processing (scst_state %d)", + req, req->scst_state); + res = 1; } - dir = scst_cmd_get_data_direction(scst_cmd); - if (dir & SCST_DATA_WRITE) { - req->is_unsolicited_data = !(req_hdr->flags & ISCSI_CMD_FINAL); - req->r2t_length = be32_to_cpu(req_hdr->data_length) - - req->pdu.datasize; - if (req->r2t_length > 0) - req->data_waiting = 1; - } else { - if (unlikely(!(req_hdr->flags & ISCSI_CMD_FINAL) || - req->pdu.datasize)) { - PRINT_ERROR("Unexpected unsolicited data (ITT %x " - "CDB %x", cmnd_itt(req), req_hdr->scb[0]); - scst_set_cmd_error(scst_cmd, - SCST_LOAD_SENSE(iscsi_sense_unexpected_unsolicited_data)); - if (scst_cmd_get_sense_buffer(scst_cmd) != NULL) - create_status_rsp(req, SAM_STAT_CHECK_CONDITION, - scst_cmd_get_sense_buffer(scst_cmd), - scst_cmd_get_sense_buffer_len(scst_cmd)); - else - create_status_rsp(req, SAM_STAT_BUSY, NULL, 0); - cmnd_reject_scsi_cmd(req); - goto out; - } - } - - req->target_task_tag = get_next_ttt(conn); - if (dir != SCST_DATA_BIDI) { - req->sg = scst_cmd_get_sg(scst_cmd); - req->sg_cnt = scst_cmd_get_sg_cnt(scst_cmd); - req->bufflen = scst_cmd_get_bufflen(scst_cmd); - } else { - req->sg = scst_cmd_get_in_sg(scst_cmd); - req->sg_cnt = scst_cmd_get_in_sg_cnt(scst_cmd); - req->bufflen = scst_cmd_get_in_bufflen(scst_cmd); - } - if (unlikely(req->r2t_length > req->bufflen)) { - PRINT_ERROR("req->r2t_length %d > req->bufflen %d", - req->r2t_length, req->bufflen); - req->r2t_length = req->bufflen; - } - - TRACE_DBG("req=%p, dir=%d, is_unsolicited_data=%d, " - "r2t_length=%d, bufflen=%d", req, dir, - req->is_unsolicited_data, req->r2t_length, req->bufflen); - - if (unlikely(!session->sess_param.immediate_data && - req->pdu.datasize)) { - PRINT_ERROR("Initiator %s violated negotiated paremeters: " - "forbidden immediate data sent (ITT %x, op %x)", - session->initiator_name, cmnd_itt(req), - req_hdr->scb[0]); - res = -EINVAL; - goto out; - } - - if (unlikely(session->sess_param.initial_r2t && - !(req_hdr->flags & ISCSI_CMD_FINAL))) { - PRINT_ERROR("Initiator %s violated negotiated paremeters: " - "initial R2T is required (ITT %x, op %x)", - session->initiator_name, cmnd_itt(req), - req_hdr->scb[0]); - res = -EINVAL; - goto out; - } - - if (req->pdu.datasize) - res = cmnd_prepare_recv_pdu(conn, req, 0, req->pdu.datasize); out: /* Aborted commands will be freed in cmnd_rx_end() */ TRACE_EXIT_RES(res); @@ -2619,18 +2645,26 @@ static int iscsi_alloc_data_buf(struct scst_cmd *cmd) static inline void iscsi_set_state_wake_up(struct iscsi_cmnd *req, int new_state) { - /* - * We use wait_event() to wait for the state change, but it checks its - * condition without any protection, so without cmnd_get() it is - * possible that req will die "immediately" after the state assignment - * and wake_up() will operate on dead data. We use the ordered version - * of cmnd_get(), because "get" must be done before the state - * assignment. - */ - cmnd_get_ordered(req); - req->scst_state = new_state; - wake_up(&req->scst_waitQ); - cmnd_put(req); + if (req->conn->rx_task == current) + req->scst_state = new_state; + else { + /* + * We wait for the state change without any protection, so + * without cmnd_get() it is possible that req will die + * "immediately" after the state assignment and + * iscsi_make_conn_rd_active() will operate on dead data. + * We use the ordered version of cmnd_get(), because "get" + * must be done before the state assignment. + */ + cmnd_get_ordered(req); + req->scst_state = new_state; + iscsi_make_conn_rd_active(req->conn); + if (unlikely(req->conn->closing)) { + TRACE_DBG("Waiking up closing conn %p", req->conn); + wake_up(&req->conn->read_state_waitQ); + } + cmnd_put(req); + } return; } diff --git a/iscsi-scst/kernel/iscsi.h b/iscsi-scst/kernel/iscsi.h index 691a8758c..131cc8d23 100644 --- a/iscsi-scst/kernel/iscsi.h +++ b/iscsi-scst/kernel/iscsi.h @@ -223,6 +223,7 @@ struct iscsi_conn { u32 read_size; int read_state; struct iovec *read_iov; + struct task_struct *rx_task; uint32_t rpadding; struct iscsi_target *target; @@ -233,6 +234,7 @@ struct iscsi_conn { struct iscsi_conn *conn_reinst_successor; struct list_head reinst_pending_cmd_list; + wait_queue_head_t read_state_waitQ; struct completion ready_to_free; /* Doesn't need any protection */ @@ -355,7 +357,6 @@ struct iscsi_cmnd { struct list_head rx_ddigest_cmd_list; struct list_head rx_ddigest_cmd_list_entry; - wait_queue_head_t scst_waitQ; int scst_state; union { struct scst_cmd *scst_cmd; @@ -414,6 +415,7 @@ extern wait_queue_head_t iscsi_wr_waitQ; extern struct iscsi_cmnd *cmnd_alloc(struct iscsi_conn *, struct iscsi_cmnd *parent); extern int cmnd_rx_start(struct iscsi_cmnd *); +extern int cmnd_rx_continue(struct iscsi_cmnd *req); extern void cmnd_rx_end(struct iscsi_cmnd *); extern void cmnd_tx_start(struct iscsi_cmnd *); extern void cmnd_tx_end(struct iscsi_cmnd *); @@ -429,6 +431,7 @@ extern void conn_reinst_finished(struct iscsi_conn *); extern int conn_add(struct iscsi_session *, struct iscsi_kern_conn_info *); extern int conn_del(struct iscsi_session *, struct iscsi_kern_conn_info *); extern int conn_free(struct iscsi_conn *); +extern void iscsi_make_conn_rd_active(struct iscsi_conn *conn); #define ISCSI_CONN_ACTIVE_CLOSE 1 #define ISCSI_CONN_DELETING 2 diff --git a/iscsi-scst/kernel/nthread.c b/iscsi-scst/kernel/nthread.c index c4d587a50..31e2bfcff 100644 --- a/iscsi-scst/kernel/nthread.c +++ b/iscsi-scst/kernel/nthread.c @@ -26,31 +26,23 @@ #include "digest.h" enum rx_state { - RX_INIT_BHS, /* Must be zero. */ + RX_INIT_BHS, /* Must be zero for better "switch" optimiztion. */ RX_BHS, - - RX_INIT_AHS, - RX_AHS, - - RX_INIT_HDIGEST, - RX_HDIGEST, - RX_CHECK_HDIGEST, - - RX_INIT_DATA, + RX_CMD_START, RX_DATA, - - RX_INIT_PADDING, - RX_PADDING, - - RX_INIT_DDIGEST, - RX_DDIGEST, - RX_CHECK_DDIGEST, - RX_END, + + RX_CMD_CONTINUE, + RX_INIT_HDIGEST, + RX_CHECK_HDIGEST, + RX_INIT_DDIGEST, + RX_CHECK_DDIGEST, + RX_AHS, + RX_PADDING, }; enum tx_state { - TX_INIT, /* Must be zero. */ + TX_INIT = 0, /* Must be zero for better "switch" optimiztion. */ TX_BHS_DATA, TX_INIT_PADDING, TX_PADDING, @@ -439,6 +431,14 @@ static void close_conn(struct iscsi_conn *conn) if (conn->read_state != RX_INIT_BHS) { struct iscsi_cmnd *cmnd = conn->read_cmnd; + + if (cmnd->scst_state == ISCSI_CMD_STATE_RX_CMD) { + TRACE_DBG("Going to wait for cmnd %p to change state " + "from RX_CMD", cmnd); + } + wait_event(conn->read_state_waitQ, + cmnd->scst_state != ISCSI_CMD_STATE_RX_CMD); + conn->read_cmnd = NULL; conn->read_state = RX_INIT_BHS; req_cmnd_release_force(cmnd, 0); @@ -615,7 +615,7 @@ static inline void iscsi_conn_init_read(struct iscsi_conn *conn, return; } -static void iscsi_conn_read_ahs(struct iscsi_conn *conn, +static void iscsi_conn_prepare_read_ahs(struct iscsi_conn *conn, struct iscsi_cmnd *cmnd) { int asize = (cmnd->pdu.ahssize + 3) & -4; @@ -643,187 +643,87 @@ static struct iscsi_cmnd *iscsi_get_send_cmnd(struct iscsi_conn *conn) return cmnd; } -static int do_recv(struct iscsi_conn *conn, int state) +/* Returns number of bytes left to receive or <0 for error */ +static int do_recv(struct iscsi_conn *conn) { - mm_segment_t oldfs; - struct msghdr msg; - int res, first_len; + int res; - sBUG_ON(conn->read_cmnd == NULL); + EXTRACHECKS_BUG_ON(conn->read_cmnd == NULL); - if (unlikely(conn->closing)) { - res = -EIO; - goto out; - } + do { + mm_segment_t oldfs; + struct msghdr msg; + int first_len; - memset(&msg, 0, sizeof(msg)); - msg.msg_iov = conn->read_msg.msg_iov; - msg.msg_iovlen = conn->read_msg.msg_iovlen; - first_len = msg.msg_iov->iov_len; + if (unlikely(conn->closing)) { + res = -EIO; + goto out; + } - oldfs = get_fs(); - set_fs(get_ds()); - res = sock_recvmsg(conn->sock, &msg, conn->read_size, - MSG_DONTWAIT | MSG_NOSIGNAL); - set_fs(oldfs); + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = conn->read_msg.msg_iov; + msg.msg_iovlen = conn->read_msg.msg_iovlen; + first_len = msg.msg_iov->iov_len; - if (res <= 0) { - switch (res) { - case -EAGAIN: - case -ERESTARTSYS: - TRACE_DBG("EAGAIN or ERESTARTSYS (%d) received for " - "conn %p", res, conn); - break; - default: - PRINT_ERROR("sock_recvmsg() failed: %d", res); - mark_conn_closed(conn); + oldfs = get_fs(); + set_fs(get_ds()); + res = sock_recvmsg(conn->sock, &msg, conn->read_size, + MSG_DONTWAIT | MSG_NOSIGNAL); + set_fs(oldfs); + + if (res > 0) { + /* + * To save some considerable effort and CPU power we + * suppose that TCP functions adjust + * conn->read_msg.msg_iov and conn->read_msg.msg_iovlen + * on amount of copied data. This BUG_ON is intended + * to catch if it is changed in the future. + */ + sBUG_ON((res >= first_len) && + (conn->read_msg.msg_iov->iov_len != 0)); + conn->read_size -= res; + if (conn->read_size != 0) { + if (res >= first_len) { + int done = 1 + ((res - first_len) >> PAGE_SHIFT); + conn->read_msg.msg_iov += done; + conn->read_msg.msg_iovlen -= done; + } + } + res = conn->read_size; + } else { + switch (res) { + case -EAGAIN: + TRACE_DBG("EAGAIN received for conn %p", conn); + res = conn->read_size; + break; + case -ERESTARTSYS: + TRACE_DBG("ERESTARTSYS received for conn %p", conn); + continue; + default: + PRINT_ERROR("sock_recvmsg() failed: %d", res); + mark_conn_closed(conn); + if (res == 0) + res = -EIO; + break; + } break; } - } else { - /* - * To save some considerable effort and CPU power we suppose - * that TCP functions adjust conn->read_msg.msg_iov and - * conn->read_msg.msg_iovlen on amount of copied data. This - * BUG_ON is intended to catch if it is changed in the future. - */ - sBUG_ON((res >= first_len) && - (conn->read_msg.msg_iov->iov_len != 0)); - conn->read_size -= res; - if (conn->read_size) { - if (res >= first_len) { - int done = - 1 + ((res - first_len) >> PAGE_SHIFT); - conn->read_msg.msg_iov += done; - conn->read_msg.msg_iovlen -= done; - } - } else - conn->read_state = state; - } + } while (res > 0); out: TRACE_EXIT_RES(res); return res; } -static int rx_hdigest(struct iscsi_conn *conn) +static int iscsi_rx_check_ddigest(struct iscsi_conn *conn) { struct iscsi_cmnd *cmnd = conn->read_cmnd; - int res = digest_rx_header(cmnd); + int res; - if (unlikely(res != 0)) { - PRINT_ERROR("rx header digest for initiator %s failed " - "(%d)", conn->session->initiator_name, res); - mark_conn_closed(conn); - } - return res; -} - -static struct iscsi_cmnd *create_cmnd(struct iscsi_conn *conn) -{ - struct iscsi_cmnd *cmnd; - - cmnd = cmnd_alloc(conn, NULL); - iscsi_conn_init_read(cmnd->conn, (void __force __user *)&cmnd->pdu.bhs, - sizeof(cmnd->pdu.bhs)); - conn->read_state = RX_BHS; - - return cmnd; -} - -/* Returns >0 for success, <=0 for error or successful finish */ -static int recv(struct iscsi_conn *conn) -{ - struct iscsi_cmnd *cmnd = conn->read_cmnd; - int hdigest, ddigest, res = 1, rc; - - TRACE_ENTRY(); - - hdigest = conn->hdigest_type & DIGEST_NONE ? 0 : 1; - ddigest = conn->ddigest_type & DIGEST_NONE ? 0 : 1; - - switch (conn->read_state) { - case RX_INIT_BHS: - sBUG_ON(cmnd != NULL); - cmnd = conn->read_cmnd = create_cmnd(conn); - case RX_BHS: - res = do_recv(conn, RX_INIT_AHS); - if (res <= 0 || conn->read_state != RX_INIT_AHS) - break; - case RX_INIT_AHS: - iscsi_cmnd_get_length(&cmnd->pdu); - if (cmnd->pdu.ahssize) { - iscsi_conn_read_ahs(conn, cmnd); - conn->read_state = RX_AHS; - } else - conn->read_state = - hdigest ? RX_INIT_HDIGEST : RX_INIT_DATA; - - if (conn->read_state != RX_AHS) - break; - case RX_AHS: - res = do_recv(conn, hdigest ? RX_INIT_HDIGEST : RX_INIT_DATA); - if (res <= 0 || conn->read_state != RX_INIT_HDIGEST) - break; - case RX_INIT_HDIGEST: - iscsi_conn_init_read(conn, - (void __force __user *)&cmnd->hdigest, sizeof(u32)); - conn->read_state = RX_HDIGEST; - case RX_HDIGEST: - res = do_recv(conn, RX_CHECK_HDIGEST); - if (res <= 0 || conn->read_state != RX_CHECK_HDIGEST) - break; - case RX_CHECK_HDIGEST: - rc = rx_hdigest(conn); - if (likely(rc == 0)) - conn->read_state = RX_INIT_DATA; - else { - res = rc; - break; - } - case RX_INIT_DATA: - rc = cmnd_rx_start(cmnd); - if (unlikely(rc != 0)) { - sBUG_ON(!conn->closing); - conn->read_state = RX_END; - res = rc; - /* cmnd will be freed in close_conn() */ - goto out; - } - conn->read_state = cmnd->pdu.datasize ? RX_DATA : RX_END; - if (conn->read_state != RX_DATA) - break; - case RX_DATA: - res = do_recv(conn, RX_INIT_PADDING); - if (res <= 0 || conn->read_state != RX_INIT_PADDING) - break; - case RX_INIT_PADDING: - { - int psz = ((cmnd->pdu.datasize + 3) & -4) - cmnd->pdu.datasize; - if (psz != 0) { - TRACE_DBG("padding %d bytes", psz); - iscsi_conn_init_read(conn, - (void __force __user *)&conn->rpadding, psz); - conn->read_state = RX_PADDING; - } else if (ddigest) - conn->read_state = RX_INIT_DDIGEST; - else - conn->read_state = RX_END; - break; - } - case RX_PADDING: - res = do_recv(conn, ddigest ? RX_INIT_DDIGEST : RX_END); - if (res <= 0 || conn->read_state != RX_INIT_DDIGEST) - break; - case RX_INIT_DDIGEST: - iscsi_conn_init_read(conn, - (void __force __user *)&cmnd->ddigest, sizeof(u32)); - conn->read_state = RX_DDIGEST; - case RX_DDIGEST: - res = do_recv(conn, RX_CHECK_DDIGEST); - if (res <= 0 || conn->read_state != RX_CHECK_DDIGEST) - break; - case RX_CHECK_DDIGEST: + res = do_recv(conn); + if (res == 0) { conn->read_state = RX_END; + if (cmnd->pdu.datasize <= 16*1024) { /* * It's cache hot, so let's compute it inline. The @@ -833,8 +733,8 @@ static int recv(struct iscsi_conn *conn) TRACE_DBG("cmnd %p, opcode %x: checking RX " "ddigest inline", cmnd, cmnd_opcode(cmnd)); cmnd->ddigest_checked = 1; - rc = digest_rx_data(cmnd); - if (unlikely(rc != 0)) { + res = digest_rx_data(cmnd); + if (unlikely(res != 0)) { mark_conn_closed(conn); goto out; } @@ -849,60 +749,182 @@ static int recv(struct iscsi_conn *conn) */ TRACE_DBG("cmnd %p, opcode %x: checking NOP RX " "ddigest", cmnd, cmnd_opcode(cmnd)); - rc = digest_rx_data(cmnd); - if (unlikely(rc != 0)) { + res = digest_rx_data(cmnd); + if (unlikely(res != 0)) { mark_conn_closed(conn); goto out; } } - break; - default: - PRINT_CRIT_ERROR("%d %x", conn->read_state, cmnd_opcode(cmnd)); - sBUG(); - } - - if (res <= 0) - goto out; - - if (conn->read_state != RX_END) - goto out; - - if (unlikely(conn->read_size)) { - PRINT_CRIT_ERROR("%d %x %d", res, cmnd_opcode(cmnd), - conn->read_size); - sBUG(); - } - - conn->read_cmnd = NULL; - conn->read_state = RX_INIT_BHS; - - cmnd_rx_end(cmnd); - - sBUG_ON(conn->read_size != 0); - - res = 0; + } out: - TRACE_EXIT_RES(res); return res; } /* No locks, conn is rd processing */ -static int process_read_io(struct iscsi_conn *conn, int *closed) +static void process_read_io(struct iscsi_conn *conn, int *closed) { + struct iscsi_cmnd *cmnd = conn->read_cmnd; int res; - do { - res = recv(conn); - if (unlikely(conn->closing)) { - start_close_conn(conn); - *closed = 1; - break; - } - } while (res > 0); + TRACE_ENTRY(); - TRACE_EXIT_RES(res); - return res; + /* In case of error cmnd will be freed in close_conn() */ + + do { + switch (conn->read_state) { + case RX_INIT_BHS: + EXTRACHECKS_BUG_ON(conn->read_cmnd != NULL); + cmnd = cmnd_alloc(conn, NULL); + conn->read_cmnd = cmnd; + iscsi_conn_init_read(cmnd->conn, + (void __force __user *)&cmnd->pdu.bhs, + sizeof(cmnd->pdu.bhs)); + conn->read_state = RX_BHS; + /* go through */ + + case RX_BHS: + res = do_recv(conn); + if (res == 0) { + iscsi_cmnd_get_length(&cmnd->pdu); + if (cmnd->pdu.ahssize == 0) { + if ((conn->hdigest_type & DIGEST_NONE) == 0) + conn->read_state = RX_INIT_HDIGEST; + else + conn->read_state = RX_CMD_START; + } else { + iscsi_conn_prepare_read_ahs(conn, cmnd); + conn->read_state = RX_AHS; + } + } + break; + + case RX_CMD_START: + res = cmnd_rx_start(cmnd); + if (res == 0) { + if (cmnd->pdu.datasize == 0) + conn->read_state = RX_END; + else + conn->read_state = RX_DATA; + } else if (res > 0) + conn->read_state = RX_CMD_CONTINUE; + else + sBUG_ON(!conn->closing); + break; + + case RX_CMD_CONTINUE: + if (cmnd->scst_state == ISCSI_CMD_STATE_RX_CMD) { + TRACE_DBG("cmnd %p is still in RX_CMD state", + cmnd); + res = 1; + break; + } + res = cmnd_rx_continue(cmnd); + if (unlikely(res != 0)) + sBUG_ON(!conn->closing); + else { + if (cmnd->pdu.datasize == 0) + conn->read_state = RX_END; + else + conn->read_state = RX_DATA; + } + break; + + case RX_DATA: + res = do_recv(conn); + if (res == 0) { + int psz = ((cmnd->pdu.datasize + 3) & -4) - cmnd->pdu.datasize; + if (psz != 0) { + TRACE_DBG("padding %d bytes", psz); + iscsi_conn_init_read(conn, + (void __force __user *)&conn->rpadding, psz); + conn->read_state = RX_PADDING; + } else if ((conn->ddigest_type & DIGEST_NONE) != 0) + conn->read_state = RX_END; + else + conn->read_state = RX_INIT_DDIGEST; + } + break; + + case RX_END: + if (unlikely(conn->read_size != 0)) { + PRINT_CRIT_ERROR("%d %x %d", res, + cmnd_opcode(cmnd), conn->read_size); + sBUG(); + } + conn->read_cmnd = NULL; + conn->read_state = RX_INIT_BHS; + + cmnd_rx_end(cmnd); + + EXTRACHECKS_BUG_ON(conn->read_size != 0); + break; + + case RX_INIT_HDIGEST: + iscsi_conn_init_read(conn, + (void __force __user *)&cmnd->hdigest, sizeof(u32)); + conn->read_state = RX_CHECK_HDIGEST; + /* go through */ + + case RX_CHECK_HDIGEST: + res = do_recv(conn); + if (res == 0) { + res = digest_rx_header(cmnd); + if (unlikely(res != 0)) { + PRINT_ERROR("rx header digest for " + "initiator %s failed (%d)", + conn->session->initiator_name, + res); + mark_conn_closed(conn); + } else + conn->read_state = RX_CMD_START; + } + break; + + case RX_INIT_DDIGEST: + iscsi_conn_init_read(conn, + (void __force __user *)&cmnd->ddigest, + sizeof(u32)); + conn->read_state = RX_CHECK_DDIGEST; + /* go through */ + + case RX_CHECK_DDIGEST: + res = iscsi_rx_check_ddigest(conn); + break; + + case RX_AHS: + res = do_recv(conn); + if (res == 0) { + if ((conn->hdigest_type & DIGEST_NONE) == 0) + conn->read_state = RX_INIT_HDIGEST; + else + conn->read_state = RX_CMD_START; + } + break; + + case RX_PADDING: + res = do_recv(conn); + if (res == 0) { + if ((conn->ddigest_type & DIGEST_NONE) == 0) + conn->read_state = RX_INIT_DDIGEST; + else + conn->read_state = RX_END; + } + break; + + default: + PRINT_CRIT_ERROR("%d %x", conn->read_state, cmnd_opcode(cmnd)); + sBUG(); + } + } while (res == 0); + + if (unlikely(conn->closing)) { + start_close_conn(conn); + *closed = 1; + } + + TRACE_EXIT(); + return; } /* @@ -920,7 +942,7 @@ static void scst_do_job_rd(void) */ while (!list_empty(&iscsi_rd_list)) { - int rc, closed = 0; + int closed = 0; struct iscsi_conn *conn = list_entry(iscsi_rd_list.next, typeof(*conn), rd_list_entry); @@ -934,7 +956,7 @@ static void scst_do_job_rd(void) #endif spin_unlock_bh(&iscsi_rd_lock); - rc = process_read_io(conn, &closed); + process_read_io(conn, &closed); spin_lock_bh(&iscsi_rd_lock); @@ -944,7 +966,7 @@ static void scst_do_job_rd(void) #ifdef CONFIG_SCST_EXTRACHECKS conn->rd_task = NULL; #endif - if ((rc == 0) || conn->rd_data_ready) { + if (conn->rd_data_ready) { list_add_tail(&conn->rd_list_entry, &iscsi_rd_list); conn->rd_state = ISCSI_CONN_RD_STATE_IN_LIST; } else diff --git a/iscsi-scst/kernel/session.c b/iscsi-scst/kernel/session.c index 27cda5755..35bcc2171 100644 --- a/iscsi-scst/kernel/session.c +++ b/iscsi-scst/kernel/session.c @@ -220,6 +220,27 @@ out_err_unlock: goto out; } +static void __session_free(struct iscsi_session *session) +{ + kfree(session->initiator_name); + kfree(session); +} + +static void iscsi_unreg_sess_done(struct scst_session *scst_sess) +{ + struct iscsi_session *session; + + TRACE_ENTRY(); + + session = (struct iscsi_session *)scst_sess_get_tgt_priv(scst_sess); + + session->scst_sess = NULL; + __session_free(session); + + TRACE_EXIT(); + return; +} + /* target_mutex supposed to be locked */ int session_free(struct iscsi_session *session, bool del) { @@ -253,6 +274,9 @@ int session_free(struct iscsi_session *session, bool del) } } + if (del) + list_del(&session->session_list_entry); + if (session->scst_sess != NULL) { /* * We must NOT call scst_unregister_session() in the waiting @@ -261,15 +285,10 @@ int session_free(struct iscsi_session *session, bool del) * and scst_mutex in SCST core (iscsi_report_aen() called by * SCST core under scst_mutex). */ - scst_unregister_session(session->scst_sess, 0, NULL); - session->scst_sess = NULL; - } - - if (del) - list_del(&session->session_list_entry); - - kfree(session->initiator_name); - kfree(session); + scst_unregister_session(session->scst_sess, 0, + iscsi_unreg_sess_done); + } else + __session_free(session); return 0; }