ft_recv_write_data() executes a WRITE once the received byte count reaches se_cmd->data_length. Frames are placed by their FCP relative offset but nothing tracks which offsets were filled, so an initiator that sends two frames at the same offset reaches data_length with part of the buffer never written: two 4096-byte frames at offset 0 of an 8192-byte write get the count to 8192 and leave the second half untouched. The buffer is not zeroed (transport_generic_new_cmd() clears zero_flag for a data CDB), so that gap keeps the previous page contents, which the backend then writes to the device for the initiator to read back. There is no oops; poisoning the buffer and sending such a pair leaves 4096 bytes of it committed unchanged, and the patched driver refuses the overlapping frame. Track the range received and drop a frame that falls inside it. With no overlapping frame accepted, the byte count reaches data_length only when the whole buffer has been written, so it stays a valid completion test; in-order and out-of-order complete writes are unaffected. Fixes: 3699d92a4d7b ("[SCSI] tcm_fc: Adding FC_FC4 provider (tcm_fc) for FCoE target (TCM - target core) support") Cc: stable@vger.kernel.org Signed-off-by: Yehyeong Lee --- drivers/target/tcm_fc/tcm_fc.h | 8 +++++ drivers/target/tcm_fc/tfc_io.c | 57 ++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/drivers/target/tcm_fc/tcm_fc.h b/drivers/target/tcm_fc/tcm_fc.h index 00e5573c62966..b75b1373a49c9 100644 --- a/drivers/target/tcm_fc/tcm_fc.h +++ b/drivers/target/tcm_fc/tcm_fc.h @@ -103,12 +103,20 @@ struct ft_lport_wwn { /* * Commands */ +#define FT_WRITE_SEGS 4 /* out-of-order write ranges tracked per cmd */ + struct ft_cmd { struct ft_sess *sess; /* session held for cmd */ struct fc_seq *seq; /* sequence in exchange mgr */ struct se_cmd se_cmd; /* Local TCM I/O descriptor */ struct fc_frame *req_frame; u32 write_data_len; /* data received on writes */ + u32 write_contig_end; /* end of the contiguous data received */ + u8 write_nseg; /* out-of-order ranges held below */ + struct ft_write_seg { + u32 off; + u32 end; + } write_seg[FT_WRITE_SEGS]; struct work_struct work; /* Local sense buffer */ unsigned char ft_sense_buffer[TRANSPORT_SENSE_BUFFER]; diff --git a/drivers/target/tcm_fc/tfc_io.c b/drivers/target/tcm_fc/tfc_io.c index 45329284f52f5..53d7c7268865d 100644 --- a/drivers/target/tcm_fc/tfc_io.c +++ b/drivers/target/tcm_fc/tfc_io.c @@ -189,6 +189,56 @@ static void ft_execute_work(struct work_struct *work) target_execute_cmd(&cmd->se_cmd); } +/* Record a frame's range; refuse one that repeats data already received. */ +static bool ft_add_write_range(struct ft_cmd *cmd, u32 off, u32 len) +{ + u32 end = off + len; + unsigned int i, j; + bool absorbed; + + if (off < cmd->write_contig_end) + return false; + for (i = 0; i < cmd->write_nseg; i++) + if (off < cmd->write_seg[i].end && cmd->write_seg[i].off < end) + return false; + + if (off != cmd->write_contig_end) { + for (i = 0; i < cmd->write_nseg; i++) { + if (cmd->write_seg[i].end == off) { + cmd->write_seg[i].end = end; + return true; + } + if (cmd->write_seg[i].off == end) { + cmd->write_seg[i].off = off; + return true; + } + } + if (cmd->write_nseg >= FT_WRITE_SEGS) + return false; + cmd->write_seg[cmd->write_nseg].off = off; + cmd->write_seg[cmd->write_nseg].end = end; + cmd->write_nseg++; + return true; + } + + cmd->write_contig_end = end; + do { + absorbed = false; + for (i = 0; i < cmd->write_nseg; i++) { + if (cmd->write_seg[i].off != cmd->write_contig_end) + continue; + cmd->write_contig_end = cmd->write_seg[i].end; + for (j = i; j + 1 < cmd->write_nseg; j++) + cmd->write_seg[j] = cmd->write_seg[j + 1]; + cmd->write_nseg--; + absorbed = true; + break; + } + } while (absorbed); + + return true; +} + /* * Receive write data frame. */ @@ -267,6 +317,11 @@ void ft_recv_write_data(struct ft_cmd *cmd, struct fc_frame *fp) goto drop; if (frame_len + rel_off > se_cmd->data_length) frame_len = se_cmd->data_length - rel_off; + if (!ft_add_write_range(cmd, rel_off, frame_len)) { + pr_err_ratelimited("%s: xid 0x%x: offset %u len %zu repeats data\n", + __func__, ep->xid, rel_off, frame_len); + goto drop; + } /* * Setup to use first mem list entry, unless no data. @@ -345,6 +400,8 @@ void ft_invl_hw_context(struct ft_cmd *cmd) */ cmd->write_data_len = lport->tt.ddp_done(lport, ep->xid); + cmd->write_contig_end = cmd->write_data_len; + cmd->write_nseg = 0; /* * Resetting same variable to indicate HW's -- 2.43.0