[PATCH] scsi: libiscsi_tcp: check that a read got the data the target claimed
Yehyeong Lee <[email protected]>
| Newsgroups | org.kernel.vger.linux-scsi,org.kernel.vger.linux-kernel,org.kernel.vger.stable |
|---|---|
| Message-ID | <[email protected]> |
A target can finish a read without sending the data. iscsi_tcp_data_in() bounds each Data-In PDU against the command buffer but never adds them up, so a completion that declares no underflow is believed and the command ends with DID_OK. The pages keep whatever they held. Over a 1 MiB pread of a file that had never been read, 999424 bytes came back as the contents of an unrelated file the same process had written earlier. pread() returned 1048576 and errno was 0. Count the payload per task. Require each Data-In to continue where the last one ended, and at completion compare the total with the buffer length, less the residual when an underflow is declared. A read can end on a Data-In or on a SCSI Response, so check both. Writes are untouched. Cc: [email protected] Signed-off-by: Yehyeong Lee <[email protected]> --- No Fixes: tag. iscsi_tcp.c had the same shape before a081c13e39b5 ("[SCSI] iscsi_tcp: split module into lib and lld"), so the check has never been there. Measured with a proxy in front of tgt. Four ways of ending a read early made it return success with the contents of an unrelated file the same process had written: a zero-length Data-In, the status in a SCSI Response, and no Data-In at all each gave 999424 of 1048576 bytes, and rewinding BufferOffset gave 737280. Each is refused now, two runs each, as is a short last PDU with no underflow declared. An honest underflow and ordinary traffic are unchanged. The check does not honour DataPDUInOrder: the target answers that key, so honouring it lets a target switch the check off - measured. A target that legitimately sends Data-In out of buffer order will now fail. Not exercised: the cxgbi offload path, and a digest session - tgt would not negotiate CRC32C. drivers/scsi/libiscsi_tcp.c | 60 +++++++++++++++++++++++++++++++++++-- include/scsi/libiscsi_tcp.h | 1 + 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/drivers/scsi/libiscsi_tcp.c b/drivers/scsi/libiscsi_tcp.c index e90805ba868fb..ce9c96f968c2a 100644 --- a/drivers/scsi/libiscsi_tcp.c +++ b/drivers/scsi/libiscsi_tcp.c @@ -467,6 +467,39 @@ void iscsi_tcp_cleanup_task(struct iscsi_task *task) } EXPORT_SYMBOL_GPL(iscsi_tcp_cleanup_task); +/** + * iscsi_tcp_check_data_in - verify the Data-In payload that was received + * @task: scsi command task + * @flags: flags of the PDU carrying the status + * @residual: residual count of that PDU + * + * A target that reports success must have sent the whole buffer, or have + * declared the shortfall as an underflow. ISCSI_FLAG_CMD_UNDERFLOW and + * ISCSI_FLAG_DATA_UNDERFLOW have the same value, so both paths can use + * this. + */ +static int iscsi_tcp_check_data_in(struct iscsi_task *task, u32 flags, + u32 residual) +{ + struct iscsi_tcp_task *tcp_task = task->dd_data; + struct scsi_cmnd *sc = task->sc; + unsigned int expected; + + if (!sc || sc->sc_data_direction == DMA_TO_DEVICE) + return 0; + + expected = sc->sdb.length; + if (flags & ISCSI_FLAG_DATA_UNDERFLOW) { + if (residual > expected) + return 0; + expected -= residual; + } + if (tcp_task->data_in_bytes == expected) + return 0; + + return ISCSI_ERR_DATALEN; +} + /** * iscsi_tcp_data_in - SCSI Data-In Response processing * @conn: iscsi connection @@ -488,7 +521,7 @@ static int iscsi_tcp_data_in(struct iscsi_conn *conn, struct iscsi_task *task) iscsi_update_cmdsn(conn->session, (struct iscsi_nopin*)rhdr); if (tcp_conn->in.datalen == 0) - return 0; + goto status; if (tcp_task->exp_datasn != datasn) { ISCSI_DBG_TCP(conn, "task->exp_datasn(%d) != rhdr->datasn(%d)" @@ -506,7 +539,17 @@ static int iscsi_tcp_data_in(struct iscsi_conn *conn, struct iscsi_task *task) return ISCSI_ERR_DATA_OFFSET; } + if (tcp_task->data_offset != tcp_task->data_in_bytes) + return ISCSI_ERR_DATA_OFFSET; + + tcp_task->data_in_bytes += tcp_conn->in.datalen; + conn->datain_pdus_cnt++; + +status: + if (rhdr->flags & ISCSI_FLAG_DATA_STATUS) + return iscsi_tcp_check_data_in(task, rhdr->flags, + be32_to_cpu(rhdr->residual_count)); return 0; } @@ -752,13 +795,25 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr) rc = __iscsi_complete_pdu(conn, hdr, NULL, 0); spin_unlock(&conn->session->back_lock); break; - case ISCSI_OP_SCSI_CMD_RSP: + case ISCSI_OP_SCSI_CMD_RSP: { + struct iscsi_scsi_rsp *rsp = (struct iscsi_scsi_rsp *)hdr; + + spin_lock(&conn->session->back_lock); + task = iscsi_itt_to_ctask(conn, hdr->itt); + if (task) + rc = iscsi_tcp_check_data_in(task, rsp->flags, + be32_to_cpu(rsp->residual_count)); + spin_unlock(&conn->session->back_lock); + if (rc) + break; + if (tcp_conn->in.datalen) { iscsi_tcp_data_recv_prep(tcp_conn); return 0; } rc = iscsi_complete_pdu(conn, hdr, NULL, 0); break; + } case ISCSI_OP_R2T: if (ahslen) { rc = ISCSI_ERR_AHSLEN; @@ -998,6 +1053,7 @@ int iscsi_tcp_task_init(struct iscsi_task *task) BUG_ON(kfifo_len(&tcp_task->r2tqueue)); tcp_task->exp_datasn = 0; + tcp_task->data_in_bytes = 0; /* Prepare PDU, optionally w/ immediate data */ ISCSI_DBG_TCP(conn, "task deq [itt 0x%x imm %d unsol %d]\n", diff --git a/include/scsi/libiscsi_tcp.h b/include/scsi/libiscsi_tcp.h index ef53d4bea28a0..b38be23b9e10b 100644 --- a/include/scsi/libiscsi_tcp.h +++ b/include/scsi/libiscsi_tcp.h @@ -66,6 +66,7 @@ struct iscsi_tcp_conn { struct iscsi_tcp_task { uint32_t exp_datasn; /* expected target's R2TSN/DataSN */ + u32 data_in_bytes; /* Data-In payload received */ int data_offset; struct iscsi_r2t_info *r2t; /* in progress solict R2T */ struct iscsi_pool r2tpool; -- 2.43.0