mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 15:22:21 -04:00
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:
@@ -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 *********************************/
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user