From: Bryam Vargas <redacted>
On SMC-R link failover, smc_switch_cursor() rewinds the send and
producer cursors to their values at the last completed CDC send, then
advances them by however far the peer's consumer cursor is ahead. That
cursor comes from the wire, so a forged one adds an arbitrary amount to
sndbuf_space and to the cursors. smc_curs_add() wraps them only once, so
the next RDMA write can take its source past the send buffer and its
destination past the peer's RMB.
A conforming peer keeps its cursor count inside its RMB and cannot
consume past the data in the send buffer. Advance only for such a count,
and no further than tx_curs_prep. The pre-failover tx_curs_sent cannot
be the bound: an earlier failover may have rewound it below the peer's
cursor.
Fixes: c6f02ebeea3a ("net/smc: switch connections to alternate link")
Cc: stable@vger.kernel.org
Signed-off-by: Bryam Vargas <redacted>
---
Bounding the copy in smc_tx_sendmsg() (3/4) does not cover this: the
RDMA write takes its offsets from the cursors.
---
net/smc/smc_core.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c
index 8829517299de..36aee89e5f5f 100644
--- a/net/smc/smc_core.c
+++ b/net/smc/smc_core.c
@@ -1034,7 +1034,7 @@ static int smc_switch_cursor(struct smc_sock *smc, struct smc_cdc_tx_pend *pend,
struct smc_wr_buf *wr_buf)
{
struct smc_connection *conn = &smc->conn;
- union smc_host_cursor cons, fin;
+ union smc_host_cursor cons, fin, prep;
int rc = 0;
int diff;
@@ -1043,12 +1043,18 @@ static int smc_switch_cursor(struct smc_sock *smc, struct smc_cdc_tx_pend *pend,
/* set prod cursor to old state, enforce tx_rdma_writes() */
smc_curs_copy(&conn->local_tx_ctrl.prod, &fin, conn);
smc_curs_copy(&cons, &conn->local_rx_ctrl.cons, conn);
+ smc_curs_copy(&prep, &conn->tx_curs_prep, conn);
- if (smc_curs_comp(conn->peer_rmbe_size, &cons, &fin) < 0) {
+ if (cons.count < conn->peer_rmbe_size &&
+ smc_curs_comp(conn->peer_rmbe_size, &cons, &fin) < 0) {
/* cons cursor advanced more than fin, and prod was set
- * fin above, so now prod is smaller than cons. Fix that.
+ * fin above, so now prod is smaller than cons. cons comes
+ * off the wire: catch up only for a count inside the peer's
+ * RMB and no further than the data in the send buffer.
*/
- diff = smc_curs_diff(conn->peer_rmbe_size, &fin, &cons);
+ diff = min(smc_curs_diff(conn->peer_rmbe_size, &fin, &cons),
+ smc_curs_diff(conn->sndbuf_desc->len,
+ &conn->tx_curs_sent, &prep));
smc_curs_add(conn->sndbuf_desc->len,
&conn->tx_curs_sent, diff);
smc_curs_add(conn->sndbuf_desc->len,
--
2.56.0