[PATCH net] net/smc: fix out-of-bounds read of rkey array in SMC-Rv2 LLC processing
From: Yehyeong Lee <hidden>
Date: 2026-07-23 09:40:59
Also in:
linux-rdma, linux-s390, lkml
Subsystem:
networking [general], shared memory communications (smc) sockets, the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, D. Wythe, Dust Li, Sidraya Jayagond, Wenjia Zhang, Linus Torvalds
SMC-Rv2 uses two receive-buffer layouts, selected by
smc_link_shared_v2_rxbuf() (i.e. lnk->wr_rx_sge_cnt > 1):
- shared (device max_recv_sge >= 2): each receive work request posts a
small primary SGE plus a spillover SGE into the per-link-group
lgr->wr_rx_buf_v2 (SMC_WR_BUF_V2_SIZE = 8 KiB); a message larger than
SMC_WR_TX_SIZE lands with its body in wr_rx_buf_v2.
- non-shared (device max_recv_sge == 1): each receive work request posts
a single SGE of wr_rx_buflen == SMC_WR_BUF_V2_SIZE, so the whole
message lands in the per-WR receive buffer.
smc_llc_rx_handler() hands the received buffer to smc_llc_enqueue(),
which copies only sizeof(union smc_llc_msg) bytes into the fixed-size
qentry->msg and queues the request; the LLC request is then processed
later from the llc_event_work work item, after smc_wr_rx_process_cqes()
has already reposted (and thus recycled) the per-WR receive buffer.
For the shared layout the rkey array survives in wr_rx_buf_v2, and
smc_llc_rmt_delete_rkey() and smc_llc_save_add_link_rkeys() read it from
there. For the non-shared layout nothing preserves the message body: both
functions instead read the rkey array from the truncated qentry copy
((struct smc_llc_msg_delete_rkey_v2 *)llc, and (u8 *)add_llc +
SMC_WR_TX_SIZE), running past the end of the ~72-byte qentry allocation.
The loop bound num_rkeys comes straight off the wire and is only capped at
SMC_LLC_RKEYS_PER_MSG_V2 (255), so a peer that sends a DELETE_RKEY or
ADD_LINK message with an oversized rkey count triggers an out-of-bounds
read of up to ~1 KiB (delete_rkey) or ~4 KiB (add_link) past a kmalloc-96
object.
BUG: KASAN: slab-out-of-bounds in smc_llc_rmt_delete_rkey+0x6a4/0x780
Read of size 4 at addr ffff8880059ee748 by task kworker/0:0H/11
Workqueue: events_highpri smc_llc_event_work
Call Trace:
dump_stack_lvl+0x53/0x70
print_report+0xd0/0x630
kasan_report+0xce/0x100
smc_llc_rmt_delete_rkey+0x6a4/0x780
smc_llc_event_handler+0xa13/0xef0
smc_llc_event_work+0x189/0x260
process_one_work+0x633/0x1030
worker_thread+0x45b/0xd10
kthread+0x2c6/0x3b0
ret_from_fork+0x36e/0x5a0
ret_from_fork_asm+0x1a/0x30
Allocated by task 43:
__kmalloc_cache_noprof+0x158/0x370
smc_llc_enqueue+0x72/0x560
smc_wr_rx_tasklet_fn+0x474/0xa80
...
The buggy address is located 0 bytes to the right of
allocated 72-byte region [ffff8880059ee700, ffff8880059ee748)
Preserve the whole received message in wr_rx_buf_v2 for the non-shared
layout as well, by copying it in smc_llc_rx_handler() before the receive
work request is reposted, and read the rkey array from wr_rx_buf_v2 in
both layouts. The reads are then bounded by the SMC_WR_BUF_V2_SIZE
buffer. This also removes a latent use-after-free on the SMC-R server
add-link path, where the non-shared code dereferenced add_llc after its
qentry had been freed by smc_llc_flow_qentry_del().
Triggering the bug requires a RoCE device that advertises
max_recv_sge == 1 (a configuration the Fixes: change added support for)
and a peer emitting an oversized rkey count. Reproduced under KASAN with
soft-RoCE (rdma_rxe) after forcing wr_rx_sge_cnt = 1.
Fixes: 27ef6a9981fe ("net/smc: support SMC-R V2 for rdma devices with max_recv_sge equals to 1")
Signed-off-by: Yehyeong Lee <redacted>
---
net/smc/smc_llc.c | 33 ++++++++++++++++++++++-----------
1 file changed, 22 insertions(+), 11 deletions(-)
diff --git a/net/smc/smc_llc.c b/net/smc/smc_llc.c
index 954b2ff1815c..1c32bf970098 100644
--- a/net/smc/smc_llc.c
+++ b/net/smc/smc_llc.c@@ -1099,9 +1099,8 @@ int smc_llc_cli_add_link(struct smc_link *link, struct smc_llc_qentry *qentry) if (rc) goto out_clear_lnk; if (lgr->smc_version == SMC_V2) { - u8 *llc_msg = smc_link_shared_v2_rxbuf(link) ? - (u8 *)lgr->wr_rx_buf_v2 : (u8 *)llc; - smc_llc_save_add_link_rkeys(link, lnk_new, llc_msg); + smc_llc_save_add_link_rkeys(link, lnk_new, + (u8 *)lgr->wr_rx_buf_v2); } else { rc = smc_llc_cli_rkey_exchange(link, lnk_new); if (rc) {
@@ -1501,9 +1500,8 @@ int smc_llc_srv_add_link(struct smc_link *link, if (rc) goto out_err; if (lgr->smc_version == SMC_V2) { - u8 *llc_msg = smc_link_shared_v2_rxbuf(link) ? - (u8 *)lgr->wr_rx_buf_v2 : (u8 *)add_llc; - smc_llc_save_add_link_rkeys(link, link_new, llc_msg); + smc_llc_save_add_link_rkeys(link, link_new, + (u8 *)lgr->wr_rx_buf_v2); } else { rc = smc_llc_srv_rkey_exchange(link, link_new); if (rc)
@@ -1812,12 +1810,15 @@ static void smc_llc_rmt_delete_rkey(struct smc_link_group *lgr) if (lgr->smc_version == SMC_V2) { struct smc_llc_msg_delete_rkey_v2 *llcv2; - if (smc_link_shared_v2_rxbuf(link)) { + 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; - } else { - llcv2 = (struct smc_llc_msg_delete_rkey_v2 *)llc; - } + /* The full message, including the rkey array, has been placed in + * wr_rx_buf_v2 for both rxbuf layouts (shared: header copy above + * + spillover DMA; non-shared: full copy in smc_llc_rx_handler()). + * Reading it from the fixed-size qentry copy would run past its + * end. + */ + llcv2 = (struct smc_llc_msg_delete_rkey_v2 *)lgr->wr_rx_buf_v2; llcv2->num_inval_rkeys = 0; max = min_t(u8, llcv2->num_rkeys, SMC_LLC_RKEYS_PER_MSG_V2);
@@ -2104,6 +2105,16 @@ static void smc_llc_rx_handler(struct ib_wc *wc, void *buf) } else { if (llc->raw.hdr.length_v2 < sizeof(*llc)) return; /* invalid message */ + /* For the non-shared v2 rxbuf layout the received message lives + * only in the per-WR receive buffer, which is reposted as soon as + * this handler returns. The LLC event handlers run later from a + * work item and read the message from wr_rx_buf_v2, so the whole + * message must be preserved there now, mirroring the shared-rxbuf + * layout where the body arrives in wr_rx_buf_v2 via spillover DMA. + */ + if (!smc_link_shared_v2_rxbuf(link)) + memcpy(link->lgr->wr_rx_buf_v2, llc, + min_t(u32, wc->byte_len, SMC_WR_BUF_V2_SIZE)); } smc_llc_enqueue(link, llc);
--
2.43.0