[PATCH net v4 2/3] net/smc: bound the peer rkey counts in SMC-Rv2 LLC messages

From: Yehyeong Lee

Date: Fri Jul 31 2026 - 03:08:53 EST


On a link whose device has max_recv_sge == 1 there is no shared v2 receive
buffer, and smc_llc_save_add_link_rkeys() takes the v2 extension from 44
bytes past the start of the queue entry's inline message:

ext = (struct smc_llc_msg_add_link_v2_ext *)(llc_msg + SMC_WR_TX_SIZE);

The entry is a 72-byte allocation and the extension starts at offset 68, so
ext->num_rkeys at offset 94 is already past it. This happens on every
SMC-Rv2 link addition, whatever the peer sends:

BUG: KASAN: slab-out-of-bounds in smc_llc_save_add_link_rkeys+0x333/0x350
Read of size 2 at addr ffff8880056406de by task smctest/106
Call Trace:
smc_llc_save_add_link_rkeys+0x333/0x350
smc_llc_cli_add_link+0xca7/0x1e80
__smc_connect+0x3f5c/0x4980
smc_connect+0x42c/0x580
__sys_connect+0xfc/0x130
Allocated by task 44:
smc_llc_enqueue+0x72/0x560
smc_wr_rx_tasklet_fn+0x474/0xa80
The buggy address is located 22 bytes to the right of
allocated 72-byte region [ffff888005640680, ffff8880056406c8)

Whatever that read finds then bounds the ext->rt[] loop, so a peer that
declares 255 rkeys reads much further. smc_llc_rmt_delete_rkey() has the
same shape for llcv2->rkey[].

Bound both loops by the buffer they read from, and skip the extension
altogether when there is no shared v2 receive buffer, because nothing
beyond the 44 inline bytes was received in that case.

Fixes: 27ef6a9981fe ("net/smc: support SMC-R V2 for rdma devices with max_recv_sge equals to 1")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Yehyeong Lee <yhlee@xxxxxxxxxxxxxxxxxx>
---
Measured over rxe with KASAN and max_recv_sge forced to 1, five test cells
(plain 1-rkey delete, delete declaring 255, plain ADD_LINK v2, ADD_LINK
declaring 255, and an SMC-Rv1 link group). Without this patch four of the
five report; with it none do. With kasan_multi_shot the unpatched kernel
reports 491 times in a single ADD_LINK run, the patched one not at all.

net/smc/smc_llc.c | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)

diff --git a/net/smc/smc_llc.c b/net/smc/smc_llc.c
index 90746cd1e29a..606d2dc8c7ec 100644
--- a/net/smc/smc_llc.c
+++ b/net/smc/smc_llc.c
@@ -1000,13 +1000,22 @@ static void smc_llc_save_add_link_rkeys(struct smc_link *link,
struct smc_link *link_new,
u8 *llc_msg)
{
+ const u32 rt_off = offsetof(struct smc_llc_msg_add_link_v2_ext, rt);
struct smc_llc_msg_add_link_v2_ext *ext;
struct smc_link_group *lgr = link->lgr;
int max, i;

+ /* The extension follows the 44 bytes of the LLC message. Without a
+ * shared v2 receive buffer nothing beyond those 44 bytes was received,
+ * so not even ext->num_rkeys is there to be read.
+ */
+ if (!smc_link_shared_v2_rxbuf(link))
+ return;
ext = (struct smc_llc_msg_add_link_v2_ext *)(llc_msg +
SMC_WR_TX_SIZE);
max = min_t(u8, ext->num_rkeys, SMC_LLC_RKEYS_PER_MSG_V2);
+ max = min_t(u32, max, (SMC_WR_BUF_V2_SIZE - SMC_WR_TX_SIZE - rt_off) /
+ sizeof(ext->rt[0]));
down_write(&lgr->rmbs_lock);
for (i = 0; i < max; i++) {
smc_rtoken_set(lgr, link->link_idx, link_new->link_idx,
@@ -1813,17 +1822,27 @@ static void smc_llc_rmt_delete_rkey(struct smc_link_group *lgr)
link = qentry->link;

if (lgr->smc_version == SMC_V2) {
+ const u32 rkey_off =
+ offsetof(struct smc_llc_msg_delete_rkey_v2, rkey);
struct smc_llc_msg_delete_rkey_v2 *llcv2;
+ u32 buf_len;

if (smc_link_shared_v2_rxbuf(link)) {
memcpy(lgr->wr_rx_buf_v2, llc, sizeof(*llc));
llcv2 = (struct smc_llc_msg_delete_rkey_v2 *)lgr->wr_rx_buf_v2;
+ buf_len = SMC_WR_BUF_V2_SIZE;
} else {
llcv2 = (struct smc_llc_msg_delete_rkey_v2 *)llc;
+ buf_len = sizeof(qentry->msg);
}
llcv2->num_inval_rkeys = 0;

max = min_t(u8, llcv2->num_rkeys, SMC_LLC_RKEYS_PER_MSG_V2);
+ /* the rkeys live in the buffer llcv2 points at, and nothing
+ * beyond it was received
+ */
+ max = min_t(u32, max, (buf_len - rkey_off) /
+ sizeof(llcv2->rkey[0]));
for (i = 0; i < max; i++) {
if (smc_rtoken_delete(link, llcv2->rkey[i]))
llcv2->num_inval_rkeys++;
--
2.43.0