On 12/08/26 4:49 am, Yehyeong Lee wrote:
> smc_llc_rmt_delete_rkey() and smc_llc_save_add_link_rkeys() read the part
> of a v2 message that does not fit into the 44-byte union smc_llc_msg, and
> both bound themselves by the size of the buffer it landed in, not by what
> arrived. On a link with a shared v2 receive buffer a 44-byte
> DELETE_RKEY_V2 declaring 255 rkeys reaches rkey[9..254] in whatever an
> earlier message left in lgr->wr_rx_buf_v2, and passes each of them to
> smc_rtoken_delete(). One of those 255 matched a registered rtoken and
> deleted it. An ADD_LINK on such a link installs up to 255 rtokens from
> the same bytes.
>
> Copy the tail into the queue entry, so its length is the length of the
> message that arrived, and declare the rkeys that fit inline as a member of
> the union instead of reaching them through a cast. The same
> DELETE_RKEY_V2 now processes the 9 rkeys it carries. The copy is limited
> to the longest tail the two functions can read, so the peer does not pick
> the size of the entry.
>
> The bound the previous patch placed on links without a shared v2 receive
> buffer is no longer needed.
>
> Fixes: 27ef6a9981fe ("net/smc: support SMC-R V2 for rdma devices with
> max_recv_sge equals to 1")
> Cc: [email protected]
> Suggested-by: D. Wythe <[email protected]>
> Signed-off-by: Yehyeong Lee <[email protected]>
> ---
> Changes since v5: added the Fixes: and Cc: stable tags; asserted that the two
> DELETE_RKEY_V2 layouts agree on offsetof(rkey); limited the copied tail to
> what the two readers can use; corrected the comment in smc_wr_init_sge().
>
> Measured over rxe with KASAN: a DELETE_RKEY_V2 carrying 12 rkeys over a link
> with a shared v2 receive buffer round-trips all 12 values, the last three
> coming from the copied tail; 8, 9 and 10 rkeys and a 44-byte message declaring
> 10 give 8, 9, 10 and 9 processed rkeys respectively. kmemleak reports nothing
> over the link-addition path, and does report the queue entry when the free
> added by patch 1 is removed again.
>
> Five runs per cell with and without the new limit: a 44-byte DELETE_RKEY_V2
> declaring 255 rkeys reports 9 processed on a link with and without a shared
> v2 receive buffer, an ADD_LINK v2 extension installs the 6 rtokens the peer
> sent, and no KASAN report appears. The only message the limit changes in
> that lab is a REQ_ADD_LINK, which copied 16 bytes that have no reader and
> now copies none. On the unpatched kernel the same DELETE_RKEY_V2 reports
> 254 and 255, and the ADD_LINK installs 255 rtokens per call.
>
Nit: There are a few extra spaces after sentence ending punctuation in
the commit message. Please remove them.
> net/smc/smc_llc.c | 125 ++++++++++++++++++++++++++++++++--------------
> net/smc/smc_wr.c | 6 +--
> 2 files changed, 91 insertions(+), 40 deletions(-)
>
> diff --git a/net/smc/smc_llc.c b/net/smc/smc_llc.c
> index 748d65186f68..393aa0af18d1 100644
> --- a/net/smc/smc_llc.c
> +++ b/net/smc/smc_llc.c
> @@ -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)
> {
> @@ -998,22 +1019,19 @@ 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;
>
> - /* Without a shared v2 receive buffer the extension is not copied
> - * into the queue entry, so not even ext->num_rkeys is there.
> - */
> - if (!smc_link_shared_v2_rxbuf(link))
> + /* 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 *)(llc_msg +
> - SMC_WR_TX_SIZE);
> + 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, (SMC_WR_BUF_V2_SIZE - SMC_WR_TX_SIZE - rt_off) /
> + 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++) {
> @@ -1107,9 +1125,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) {
> @@ -1510,9 +1526,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)
> @@ -1561,7 +1575,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 */
> @@ -1597,7 +1612,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)
> @@ -1819,27 +1835,28 @@ 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);
> - }
> + struct smc_llc_msg_delete_rkey_v2_inline *llcv2;
> +
> + /* 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);
> - /* bound by the buffer llcv2 points at */
> - max = min_t(u32, max, (buf_len - rkey_off) /
> - sizeof(llcv2->rkey[0]));
> + 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));
> @@ -2080,18 +2097,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;
> +
> + /* 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_obj(*qentry, GFP_ATOMIC);
> + 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) &&
> @@ -2123,7 +2174,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
> *********************************/
> diff --git a/net/smc/smc_wr.c b/net/smc/smc_wr.c
> index 59c92b46945c..97ba46893b17 100644
> --- a/net/smc/smc_wr.c
> +++ b/net/smc/smc_wr.c
> @@ -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;
Patch looks good to me.
Reviewed-by: Sidraya Jayagond <[email protected]>