Merge branch 'net-smc-fix-out-of-bounds-and-use-after-free-in-smc-rv2-llc-processing'

Yehyeong Lee says:

====================
net/smc: fix out-of-bounds and use-after-free in SMC-Rv2 LLC processing

Patch 1 fixes a use-after-free of the LLC queue entry in
smc_llc_srv_add_link(), patch 2 bounds the peer's rkey counts, and patch 3
carries the tail of an oversized v2 message in the queue entry so that both
readers are bounded by what arrived. All three are tagged for stable: a
tree that takes 1 and 2 without 3 still deletes rkeys read from whatever an
earlier message left in the shared receive buffer.
====================

Link: https://patch.msgid.link/20260819023306.644849-1-yhlee@isslab.korea.ac.kr
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski
2026-08-22 13:25:54 -07:00
2 changed files with 98 additions and 30 deletions

View File

@@ -157,6 +157,7 @@ struct smc_llc_msg_confirm_rkey { /* type 0x06 */
};
#define SMC_LLC_DEL_RKEY_MAX 8
#define SMC_LLC_DEL_RKEY_V2_INLINE 9
#define SMC_LLC_FLAG_RKEY_RETRY 0x10
#define SMC_LLC_FLAG_RKEY_NEG 0x20
@@ -177,6 +178,15 @@ struct smc_llc_msg_delete_rkey_v2 { /* type 0x29 */
__be32 rkey[];
};
/* the leading rkeys of a DELETE_RKEY_V2 fit into union smc_llc_msg */
struct smc_llc_msg_delete_rkey_v2_inline { /* type 0x29 */
struct smc_llc_hdr hd;
u8 num_rkeys;
u8 num_inval_rkeys;
u8 reserved[2];
__be32 rkey[SMC_LLC_DEL_RKEY_V2_INLINE];
};
union smc_llc_msg {
struct smc_llc_msg_confirm_link confirm_link;
struct smc_llc_msg_add_link add_link;
@@ -186,6 +196,7 @@ union smc_llc_msg {
struct smc_llc_msg_confirm_rkey confirm_rkey;
struct smc_llc_msg_delete_rkey delete_rkey;
struct smc_llc_msg_delete_rkey_v2_inline delete_rkey_v2;
struct smc_llc_msg_test_link test_link;
struct {
@@ -194,15 +205,25 @@ union smc_llc_msg {
} raw;
};
static_assert(SMC_LLC_DEL_RKEY_V2_INLINE ==
(sizeof(union smc_llc_msg) -
offsetof(struct smc_llc_msg_delete_rkey_v2, rkey)) /
sizeof(__be32));
static_assert(offsetof(struct smc_llc_msg_delete_rkey_v2_inline, rkey) ==
offsetof(struct smc_llc_msg_delete_rkey_v2, rkey));
#define SMC_LLC_FLAG_RESP 0x80
struct smc_llc_qentry {
struct list_head list;
struct smc_link *link;
u16 body_len;
union smc_llc_msg msg;
u8 body[] __counted_by(body_len);
};
static void smc_llc_enqueue(struct smc_link *link, union smc_llc_msg *llc);
static void smc_llc_enqueue(struct smc_link *link, union smc_llc_msg *llc,
u32 byte_len);
struct smc_llc_qentry *smc_llc_flow_qentry_clr(struct smc_llc_flow *flow)
{
@@ -999,15 +1020,20 @@ static int smc_llc_cli_conf_link(struct smc_link *link,
static void smc_llc_save_add_link_rkeys(struct smc_link *link,
struct smc_link *link_new,
u8 *llc_msg)
struct smc_llc_qentry *qentry)
{
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;
ext = (struct smc_llc_msg_add_link_v2_ext *)(llc_msg +
SMC_WR_TX_SIZE);
/* the rkey count itself is only there if enough bytes arrived */
if (qentry->body_len < rt_off)
return;
ext = (struct smc_llc_msg_add_link_v2_ext *)qentry->body;
max = min_t(u8, ext->num_rkeys, SMC_LLC_RKEYS_PER_MSG_V2);
max = min_t(u32, max, (qentry->body_len - 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,
@@ -1100,9 +1126,7 @@ 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, qentry);
} else {
rc = smc_llc_cli_rkey_exchange(link, lnk_new);
if (rc) {
@@ -1482,7 +1506,7 @@ int smc_llc_srv_add_link(struct smc_link *link,
}
add_llc = &qentry->msg.add_link;
if (add_llc->hd.flags & SMC_LLC_FLAG_ADD_LNK_REJ) {
smc_llc_flow_qentry_del(&lgr->llc_flow_lcl);
smc_llc_flow_qentry_clr(&lgr->llc_flow_lcl);
rc = -ENOLINK;
goto out_err;
}
@@ -1493,7 +1517,8 @@ int smc_llc_srv_add_link(struct smc_link *link,
lgr_new_t = SMC_LGR_ASYMMETRIC_PEER;
}
smc_llc_save_add_link_info(link_new, add_llc);
smc_llc_flow_qentry_del(&lgr->llc_flow_lcl);
/* add_llc still points into qentry, so only detach it here */
smc_llc_flow_qentry_clr(&lgr->llc_flow_lcl);
rc = smc_ib_ready_link(link_new);
if (rc)
@@ -1502,9 +1527,7 @@ 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, qentry);
} else {
rc = smc_llc_srv_rkey_exchange(link, link_new);
if (rc)
@@ -1513,14 +1536,14 @@ int smc_llc_srv_add_link(struct smc_link *link,
rc = smc_llc_srv_conf_link(link, link_new, lgr_new_t);
if (rc)
goto out_err;
kfree(ini);
return 0;
goto out;
out_err:
if (link_new) {
link_new->state = SMC_LNK_INACTIVE;
smcr_link_clear(link_new, false);
}
out:
kfree(qentry);
kfree(ini);
if (send_req_add_link_resp)
smc_llc_send_req_add_link_response(req_qentry);
@@ -1553,7 +1576,8 @@ void smc_llc_add_link_local(struct smc_link *link)
add_llc.hd.common.llc_type = SMC_LLC_ADD_LINK;
smc_llc_init_msg_hdr(&add_llc.hd, link->lgr, sizeof(add_llc));
/* no dev and port needed */
smc_llc_enqueue(link, (union smc_llc_msg *)&add_llc);
smc_llc_enqueue(link, (union smc_llc_msg *)&add_llc,
sizeof(union smc_llc_msg));
}
/* worker to process an add link message */
@@ -1589,7 +1613,8 @@ void smc_llc_srv_delete_link_local(struct smc_link *link, u8 del_link_id)
del_llc.link_num = del_link_id;
del_llc.reason = htonl(SMC_LLC_DEL_LOST_PATH);
del_llc.hd.flags |= SMC_LLC_FLAG_DEL_LINK_ORDERLY;
smc_llc_enqueue(link, (union smc_llc_msg *)&del_llc);
smc_llc_enqueue(link, (union smc_llc_msg *)&del_llc,
sizeof(union smc_llc_msg));
}
static void smc_llc_process_cli_delete_link(struct smc_link_group *lgr)
@@ -1811,19 +1836,28 @@ static void smc_llc_rmt_delete_rkey(struct smc_link_group *lgr)
link = qentry->link;
if (lgr->smc_version == SMC_V2) {
struct smc_llc_msg_delete_rkey_v2 *llcv2;
struct smc_llc_msg_delete_rkey_v2_inline *llcv2;
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 leading SMC_LLC_DEL_RKEY_V2_INLINE rkeys are declared in
* the message itself, any further ones were received into
* qentry->body.
*/
llcv2 = &qentry->msg.delete_rkey_v2;
llcv2->num_inval_rkeys = 0;
max = min_t(u8, llcv2->num_rkeys, SMC_LLC_RKEYS_PER_MSG_V2);
max = min_t(u32, max, SMC_LLC_DEL_RKEY_V2_INLINE +
qentry->body_len / sizeof(__be32));
for (i = 0; i < max; i++) {
if (smc_rtoken_delete(link, llcv2->rkey[i]))
__be32 rkey;
if (i < SMC_LLC_DEL_RKEY_V2_INLINE)
rkey = llcv2->rkey[i];
else
memcpy(&rkey, qentry->body +
(i - SMC_LLC_DEL_RKEY_V2_INLINE) *
sizeof(rkey), sizeof(rkey));
if (smc_rtoken_delete(link, rkey))
llcv2->num_inval_rkeys++;
}
memset(&llc->rkey[0], 0, sizeof(llc->rkey));
@@ -2067,18 +2101,52 @@ static void smc_llc_rx_response(struct smc_link *link,
wake_up(&link->lgr->llc_msg_waiter);
}
static void smc_llc_enqueue(struct smc_link *link, union smc_llc_msg *llc)
/* the longest tail either reader of qentry->body can use */
static u32 smc_llc_max_body_len(union smc_llc_msg *llc)
{
switch (llc->raw.hdr.common.llc_type) {
case SMC_LLC_ADD_LINK:
return offsetof(struct smc_llc_msg_add_link_v2_ext, rt) +
SMC_LLC_RKEYS_PER_MSG_V2 *
sizeof(struct smc_llc_msg_add_link_cont_rt);
case SMC_LLC_DELETE_RKEY:
return (SMC_LLC_RKEYS_PER_MSG_V2 -
SMC_LLC_DEL_RKEY_V2_INLINE) * sizeof(__be32);
default:
return 0;
}
}
static void smc_llc_enqueue(struct smc_link *link, union smc_llc_msg *llc,
u32 byte_len)
{
struct smc_link_group *lgr = link->lgr;
struct smc_llc_qentry *qentry;
unsigned long flags;
u16 body_len = 0;
qentry = kmalloc_obj(*qentry, GFP_ATOMIC);
/* V2 messages can be longer than the inline union smc_llc_msg. Carry
* the remainder in the qentry itself, so that its lifetime and its
* length match the message the peer actually sent.
*/
if (lgr->smc_version == SMC_V2 && byte_len > SMC_WR_TX_SIZE)
body_len = min_t(u32, byte_len, SMC_WR_BUF_V2_SIZE) -
SMC_WR_TX_SIZE;
body_len = min_t(u32, body_len, smc_llc_max_body_len(llc));
qentry = kmalloc_flex(*qentry, body, body_len, GFP_ATOMIC);
if (!qentry)
return;
qentry->body_len = body_len;
qentry->link = link;
INIT_LIST_HEAD(&qentry->list);
memcpy(&qentry->msg, llc, sizeof(union smc_llc_msg));
if (body_len) {
u8 *src = smc_link_shared_v2_rxbuf(link) ?
(u8 *)lgr->wr_rx_buf_v2 : (u8 *)llc;
memcpy(qentry->body, src + SMC_WR_TX_SIZE, body_len);
}
/* process responses immediately */
if ((llc->raw.hdr.flags & SMC_LLC_FLAG_RESP) &&
@@ -2110,7 +2178,7 @@ static void smc_llc_rx_handler(struct ib_wc *wc, void *buf)
return; /* invalid message */
}
smc_llc_enqueue(link, llc);
smc_llc_enqueue(link, llc, wc->byte_len);
}
/***************************** worker, utils *********************************/

View File

@@ -602,9 +602,9 @@ static void smc_wr_init_sge(struct smc_link *lnk)
/* With SMC-Rv2 there can be messages larger than SMC_WR_TX_SIZE.
* Each ib_recv_wr gets 2 sges, the second one is a spillover buffer
* and the same buffer for all sges. When a larger message arrived then
* the content of the first small sge is copied to the beginning of
* the larger spillover buffer, allowing easy data mapping.
* and the same buffer for all sges. The spillover sge starts at
* SMC_WR_TX_SIZE, so the leading bytes of that buffer are never
* written.
*/
for (i = 0; i < lnk->wr_rx_cnt; i++) {
int x = i * lnk->wr_rx_sge_cnt;