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@vger.kernel.org Signed-off-by: Yehyeong Lee --- 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