From: sashiko-bot@kernel.org
To: "Xin Tian" <tianx@yunsilicon.com>
Cc: linux-rdma@vger.kernel.org
Subject: Re: [PATCH v1 13/15] RDMA/xsc: add QP verbs
Date: Thu, 05 Nov 2026 16:09:37 +0000 [thread overview]
Message-ID: <sashiko-outbox-199435@kernel.org> (raw)
In-Reply-To: <20261104155720.37053-14-tianx@yunsilicon.com>
Thank you for your contribution! Sashiko AI review found 12 potential issue(s) to consider:
- [Critical] RDMA/xsc: heap out-of-bounds write in create_user_qp() when hw_npages is 0
- [High] RDMA/xsc: NULL CQ dereference in create_qp_common() debug formatting
- [High] RDMA/xsc: 32-bit overflow in create_user_qp() sq.offset calculation
- [High] RDMA/xsc: incomplete and racy err_state_qp_list cleanup in xsc_ib_destroy_qp()
- [High] RDMA/xsc: leaked error nodes and unlocked WQ reset in xsc_ib_modify_qp()
- [High] RDMA/xsc: SRQ WR leak and qp_num corruption across QP state transitions
- [High] RDMA/xsc: uninitialized kernel memory leak to user in xsc_ib_query_qp()
- [Medium] RDMA/xsc: insufficient udata->inlen checks in create_user_qp()
- [Medium] RDMA/xsc: uninitialized stack variable offset logged in create_user_qp()
- [Medium] RDMA/xsc: integer truncation in set_rq_size() before max_wqes check
- [Medium] RDMA/xsc: IB_MTU_8192 misreported as IB_MTU_4096 in xsc_ib_query_qp()
- [Medium] RDMA/xsc: premature ctx mutation and wrong page_shift in QP verbs
--
commit f535ae7002b2233def13ec88aba5f48f2448d9b9
Author: Xin Tian <tianx@yunsilicon.com>
RDMA/xsc: add QP verbs
This commit implements queue pair creation, modification, query, and
destruction callbacks for the xsc IB driver, supporting RC, MAD QP0/QP1, and
raw packet queue pairs for both kernel and user-space callers.
> diff --git a/drivers/infiniband/hw/xsc/qp.c b/drivers/infiniband/hw/xsc/qp.c
> index a41d880cf03d9..0dfb102949070 100644
> --- a/drivers/infiniband/hw/xsc/qp.c
> +++ b/drivers/infiniband/hw/xsc/qp.c
[ ... ]
> +static int set_rq_size(struct xsc_ib_dev *dev, struct ib_qp_init_attr *init_attr,
> + int has_rq, struct xsc_ib_qp *qp, struct xsc_ib_create_qp *ucmd)
> +{
> + u32 wqe_cnt = init_attr->cap.max_recv_wr ?
> + roundup_pow_of_two(init_attr->cap.max_recv_wr) : 0;
> +
> + if (init_attr->srq)
> + wqe_cnt = to_xsrq(init_attr->srq)->xsrq.max;
> +
> + if (wqe_cnt > dev->xdev->caps.max_wqes) {
> + ibdev_err(&dev->ib_dev, "max_recv_wr:%d exceed max rq depth\n",
> + init_attr->cap.max_recv_wr);
> + return -EINVAL;
> + }
[ ... ]
> +static int calc_sq_size(struct xsc_ib_dev *dev, struct ib_qp_init_attr *attr,
> + struct xsc_ib_qp *qp)
> +{
> + int wqe_size;
> + size_t wq_size;
> +
> + if (!attr->cap.max_send_wr) {
> + ibdev_err(&dev->ib_dev, "invalid max_send_wr:%d\n", attr->cap.max_send_wr);
> + return -1;
> + }
> +
> + wqe_size = 1 << dev->xdev->caps.send_wqe_shift;
> + qp->max_inline_data = (dev->xdev->caps.send_ds_num - 2) * sizeof(struct xsc_wqe_data_seg);
> + attr->cap.max_inline_data = qp->max_inline_data;
> +
> + attr->cap.max_send_wr = min_t(u32, attr->cap.max_send_wr, dev->xdev->caps.max_wqes);
> + qp->sq.wqe_cnt = roundup_pow_of_two(attr->cap.max_send_wr);
[Severity: Medium]
In set_rq_size(), if init_attr->cap.max_recv_wr is greater than 0x80000000U
and init_attr->srq is NULL, roundup_pow_of_two(init_attr->cap.max_recv_wr)
returns 1UL << 32 on 64-bit platforms (or invokes shift undefined behavior on
32-bit platforms) and truncates to 0 when assigned to u32 wqe_cnt.
When wqe_cnt truncates to 0, the wqe_cnt > dev->xdev->caps.max_wqes check is
bypassed and, for kernel QPs, qp->rq.wqe_cnt and qp->rq.max_post remain 0
even though qp->has_rq is 1.
Also, in calc_sq_size(), attr->cap.max_send_wr is silently clamped to
dev->xdev->caps.max_wqes with min_t() instead of returning -EINVAL when the
requested depth exceeds device limits.
Should set_rq_size() and calc_sq_size() validate init_attr->cap.max_recv_wr
and attr->cap.max_send_wr against dev->xdev->caps.max_wqes before calling
roundup_pow_of_two()?
[ ... ]
> +static int create_user_qp(struct xsc_ib_dev *dev, struct ib_pd *pd,
> + struct xsc_ib_qp *qp, struct ib_udata *udata,
> + struct xsc_ib_create_qp *ucmd,
> + struct xsc_create_qp_mbox_in **in, int *inlen)
> +{
> + int page_shift;
> + int npages;
> + u32 offset;
> + int ncont;
> + int err;
> + int hw_npages;
> + u32 sq_shift, rq_shift;
> + u32 sq_wqe_cnt, rq_wqe_cnt;
> + struct ib_umem_dmabuf *umem_dmabuf;
> +
> + ibdev_dbg(&dev->ib_dev, "buf_addr:0x%lx db_addr:0x%lx sq cnt:%u, rq cnt:%u, rq shift:%u\n",
> + (uintptr_t)ucmd->buf_addr, (uintptr_t)ucmd->db_addr,
> + ucmd->sq_wqe_count, ucmd->rq_wqe_count, ucmd->rq_wqe_shift);
> +
> + sq_shift = dev->xdev->caps.send_wqe_shift;
> + rq_shift = ucmd->rq_wqe_shift;
> + if (sq_shift < XSC_BASE_WQE_SHIFT || sq_shift > 31 ||
> + (ucmd->rq_wqe_count &&
> + (rq_shift < XSC_BASE_WQE_SHIFT || rq_shift > 31))) {
> + ibdev_err(&dev->ib_dev, "invalid user queue parameters\n");
> + return -EINVAL;
> + }
> +
> + if (ucmd->sq_wqe_count) {
> + if (!is_power_of_2(ucmd->sq_wqe_count) ||
> + ucmd->sq_wqe_count < (1u << (sq_shift - XSC_BASE_WQE_SHIFT))) {
> + ibdev_err(&dev->ib_dev, "invalid user sq parameters\n");
> + return -EINVAL;
> + }
> + sq_wqe_cnt = ucmd->sq_wqe_count >> (sq_shift - XSC_BASE_WQE_SHIFT);
> + } else {
> + sq_wqe_cnt = 0;
> + }
> +
> + if (ucmd->rq_wqe_count) {
> + if (!is_power_of_2(ucmd->rq_wqe_count) ||
> + ucmd->rq_wqe_count < (1u << (rq_shift - XSC_BASE_WQE_SHIFT))) {
> + ibdev_err(&dev->ib_dev, "invalid user rq parameters\n");
> + return -EINVAL;
> + }
> + rq_wqe_cnt = ucmd->rq_wqe_count >> (rq_shift - XSC_BASE_WQE_SHIFT);
> + } else {
> + rq_wqe_cnt = 0;
> + }
> +
> + if (sq_wqe_cnt > dev->xdev->caps.max_wqes ||
> + rq_wqe_cnt > dev->xdev->caps.max_wqes) {
> + ibdev_err(&dev->ib_dev, "user queue depth exceeds max %u\n",
> + dev->xdev->caps.max_wqes);
> + return -EINVAL;
> + }
> +
> + qp->sq.ds_cnt = ucmd->sq_wqe_count;
> + qp->sq.wqe_cnt = sq_wqe_cnt;
> + qp->sq.wqe_shift = sq_wqe_cnt ? sq_shift : 0;
> + qp->sq.max_gs = dev->xdev->caps.send_ds_num - XSC_CTRL_SEG_NUM - XSC_RADDR_SEG_NUM;
> + qp->rq.ds_cnt = ucmd->rq_wqe_count;
> + qp->rq.wqe_cnt = rq_wqe_cnt;
> + qp->rq.wqe_shift = rq_wqe_cnt ? rq_shift : 0;
> + if (rq_wqe_cnt) {
> + qp->rq.max_gs = 1;
> + qp->rq.max_post = rq_wqe_cnt;
> + }
> +
> + qp->rq.offset = 0;
> + qp->sq.offset = (u32)qp->rq.wqe_cnt << qp->rq.wqe_shift;
> + qp->buf_size = (u64)qp->sq.offset +
> + ((u64)qp->sq.wqe_cnt << qp->sq.wqe_shift);
> + if (qp->buf_size > (u64)U16_MAX * XSC_PAGE_SIZE_4K) {
> + ibdev_err(&dev->ib_dev, "QP buffer size too large\n");
> + return -EINVAL;
> + }
[Severity: High]
Can the 32-bit shift in qp->sq.offset = (u32)qp->rq.wqe_cnt <<
qp->rq.wqe_shift overflow and bypass the qp->buf_size check in
create_user_qp()?
Because rq_shift is permitted up to 31 and struct xsc_ib_wq defines offset as
a signed 32-bit int, a user supplying ucmd->rq_wqe_shift = 31 and
ucmd->rq_wqe_count = 1U << 28 gets rq_wqe_cnt = 2 (which passes the max_wqes
check), while (u32)qp->rq.wqe_cnt << qp->rq.wqe_shift wraps in 32-bit
arithmetic to 0.
When qp->sq.offset wraps to 0, qp->buf_size only accounts for the SQ size and
passes the U16_MAX * XSC_PAGE_SIZE_4K check, so the driver pins and populates
PAS entries only for the SQ while create_qp_common() programs
in->req.log_rq_sz = ilog2(qp->rq.ds_cnt) (a 4 GiB RQ) into the hardware.
Similarly, if ucmd->sq_wqe_count is 0, create_user_qp() allocates 0 bytes for
the SQ in qp->buf_size, whereas create_qp_common() still sets
in->req.log_sq_sz = ilog2(0x80) in the firmware request.
> + hw_npages = DIV_ROUND_UP(qp->buf_size, XSC_PAGE_SIZE_4K);
> +
> + if (udata->inlen >= offsetofend(typeof(*ucmd), dmabuf_fd) &&
> + ucmd->dmabuf_fd != -1) {
> + if (ucmd->dmabuf_sz < qp->buf_size) {
[Severity: Medium]
In create_user_qp(), does the dmabuf check verify enough of udata->inlen
before reading ucmd->dmabuf_sz?
Checking udata->inlen >= offsetofend(typeof(*ucmd), dmabuf_fd) only ensures
the buffer covers dmabuf_fd (36 bytes), not the following dmabuf_sz field (up
to 48 bytes). Also, in create_qp_common(), ib_copy_from_udata() copies
min(udata->inlen, sizeof(ucmd)) without checking that udata->inlen is at
least offsetofend(struct xsc_ib_create_qp, flags) for the mandatory base
fields.
Should create_qp_common() reject udata->inlen smaller than
offsetofend(typeof(ucmd), flags) and create_user_qp() check
offsetofend(typeof(*ucmd), dmabuf_sz)?
> + ibdev_err(&dev->ib_dev, "dmabuf_sz %llu < qp buf_size %llu\n",
> + ucmd->dmabuf_sz, qp->buf_size);
> + return -EINVAL;
> + }
> +
> + umem_dmabuf = ib_umem_dmabuf_get_pinned(&dev->ib_dev, 0, ucmd->dmabuf_sz,
> + ucmd->dmabuf_fd, IB_ACCESS_LOCAL_WRITE);
[ ... ]
> + err = xsc_ib_get_buf_offset(ucmd->buf_addr, page_shift, &offset);
> + if (err) {
> + ibdev_err(&dev->ib_dev, "bad offset:%d\n", offset);
> + goto err_umem;
> + }
[Severity: Medium]
In create_user_qp(), offset is declared on the stack without initialization.
Looking at xsc_ib_get_buf_offset() in drivers/infiniband/hw/xsc/mem.c:
if (buf_off & off_mask)
return -EINVAL;
*offset = buf_off >> ilog2(off_size);
return 0;
When ucmd->buf_addr fails the alignment check, xsc_ib_get_buf_offset()
returns -EINVAL without initializing *offset, so ibdev_err() prints
uninitialized kernel stack memory. Also, when offset is non-zero, hw_npages =
DIV_ROUND_UP(qp->buf_size, XSC_PAGE_SIZE_4K) does not account for the
sub-page offset if the buffer crosses a 4K page boundary, and offset is never
passed to firmware.
> + ibdev_dbg(&dev->ib_dev, "npage:%d, page_shift:%d, ncont:%d, offset:%u, hw_npages %d\n",
> + npages, page_shift, ncont, offset, hw_npages);
> +
> + *inlen = sizeof(**in) + (size_t)hw_npages * sizeof((*in)->req.pas[0]);
> + *in = xsc_vzalloc(*inlen);
> + if (!*in) {
> + err = -ENOMEM;
> + goto err_umem;
> + }
> + xsc_ib_populate_pas(dev, qp->umem, page_shift, (*in)->req.pas, hw_npages, true);
[Severity: Critical]
Can create_user_qp() trigger an out-of-bounds heap write into (*in)->req.pas
when hw_npages is 0?
If userspace passes ucmd->sq_wqe_count == 0 and ucmd->rq_wqe_count == 0 (or
triggers the 32-bit overflow in qp->sq.offset with sq_wqe_count == 0),
qp->buf_size is 0 and hw_npages is 0. If userspace also provides a valid
ucmd->dmabuf_fd != -1 with ucmd->dmabuf_sz >= PAGE_SIZE, the check
ucmd->dmabuf_sz < qp->buf_size is false and ib_umem_dmabuf_get_pinned()
returns a non-empty umem.
create_user_qp() then allocates *in with *inlen = sizeof(**in) (zero elements
in req.pas[]) and calls xsc_ib_populate_pas() -> __xsc_ib_populate_pas() with
num_pages = 0:
drivers/infiniband/hw/xsc/mem.c:__xsc_ib_populate_pas()
pas[idx] = cpu_to_be64(cur);
...
i++;
/* Stop after num_pages reached */
if (i >> shift >= offset + num_pages)
return;
Because __xsc_ib_populate_pas() writes pas[0] before checking whether
num_pages has been reached, this overwrites 8 bytes past the end of *in.
Should create_user_qp() reject qp->buf_size == 0 (or both sq_wqe_count == 0
and rq_wqe_count == 0)?
[ ... ]
> +static int create_qp_common(struct xsc_ib_dev *dev, struct ib_pd *pd,
> + struct ib_qp_init_attr *init_attr,
> + struct ib_udata *udata, struct xsc_ib_qp *qp)
> +{
[ ... ]
> + if (init_attr->send_cq) {
> + qp->send_cq = init_attr->send_cq;
> + in->req.cqn_send = to_xcq(init_attr->send_cq)->xcq.cqn;
> + in->req.cqn_send = cpu_to_be16(in->req.cqn_send);
> + }
> +
> + if (init_attr->recv_cq) {
> + qp->recv_cq = init_attr->recv_cq;
> + in->req.cqn_recv = to_xcq(init_attr->recv_cq)->xcq.cqn;
> + in->req.cqn_recv = cpu_to_be16(in->req.cqn_recv);
> + }
[ ... ]
> + err = xsc_core_create_qp(dev->xdev, &qp->xqp, in, inlen);
> + if (err) {
> + ibdev_err(&dev->ib_dev, "create qp failed, err=%d\n", err);
> + goto err_create;
> + }
> +
> + qp->doorbell_qpn = qp->xqp.qpn;
> +
> + qp->xqp.qp_type = init_attr->qp_type;
> + ret += snprintf(ptr + ret, 256 - ret, "pdn=%d,", to_xpd(pd)->pdn);
> + ret += snprintf(ptr + ret, 256 - ret, "log_rq_sz=%d,", in->req.log_rq_sz);
> + ret += snprintf(ptr + ret, 256 - ret, "log_sq_sz=%d,", in->req.log_sq_sz);
> + ret += snprintf(ptr + ret, 256 - ret, "scqn=%d,", to_xcq(init_attr->send_cq)->xcq.cqn);
> + ret += snprintf(ptr + ret, 256 - ret, "rcqn=%d", to_xcq(init_attr->recv_cq)->xcq.cqn);
[Severity: High]
Earlier in create_qp_common(), init_attr->send_cq and init_attr->recv_cq are
checked for NULL before dereferencing, and get_cqs() also guards both
pointers.
However, these two snprintf() calls unconditionally dereference
to_xcq(init_attr->send_cq) and to_xcq(init_attr->recv_cq). Since ibcq is at
offset 0 of struct xsc_ib_cq, won't creating a QP with a NULL send_cq or
recv_cq cause a NULL pointer dereference here right after xsc_core_create_qp()
has already created the QP in firmware?
[ ... ]
> +int xsc_ib_destroy_qp(struct ib_qp *qp, struct ib_udata *udata)
> +{
> + struct xsc_ib_dev *dev = to_xdev(qp->device);
> + struct xsc_ib_qp *xqp = to_xqp(qp);
> + struct xsc_err_state_qp_node *tmp = NULL, *err_rq_node = NULL, *err_sq_node = NULL;
> + struct xsc_srq_qp_node *qp_node = NULL, *tmp_qp_node = NULL;
> + unsigned long flags;
> + struct xsc_ib_srq *xsrq;
> +
> + if (qp->qp_type == IB_QPT_RC) {
> + if (xqp->recv_cq) {
> + spin_lock_irq(&to_xcq(xqp->recv_cq)->err_state_qp_lock);
> + list_for_each_entry_safe(err_rq_node, tmp,
> + &to_xcq(xqp->recv_cq)->err_state_qp_list, entry) {
> + if (err_rq_node->qp_id == xqp->xqp.qpn && !err_rq_node->is_sq) {
> + list_del(&err_rq_node->entry);
> + kfree(err_rq_node);
> + }
> + }
> + spin_unlock_irq(&to_xcq(xqp->recv_cq)->err_state_qp_lock);
> + }
> +
> + if (xqp->send_cq) {
> + spin_lock_irq(&to_xcq(xqp->send_cq)->err_state_qp_lock);
> + list_for_each_entry_safe(err_sq_node, tmp,
> + &to_xcq(xqp->send_cq)->err_state_qp_list, entry) {
> + if (err_sq_node->qp_id == xqp->xqp.qpn && err_sq_node->is_sq) {
> + list_del(&err_sq_node->entry);
> + kfree(err_sq_node);
> + }
> + }
> + spin_unlock_irq(&to_xcq(xqp->send_cq)->err_state_qp_lock);
> + }
> + }
[Severity: High]
Can xsc_ib_destroy_qp() leak err_state_qp_node entries or race with concurrent
CQ polling when cleaning err_state_qp_list?
First, xsc_ib_modify_qp() and xsc_poll_one() call xsc_ib_err_state_qp() for
any kernel QP transitioning to XSC_QP_STATE_ERR regardless of qp_type, but
xsc_ib_destroy_qp() only purges err_state_qp_list when qp->qp_type ==
IB_QPT_RC. If a non-RC kernel QP (such as IB_QPT_GSI, IB_QPT_SMI, or
IB_QPT_RAW_PACKET) enters IB_QPS_ERR and is destroyed, its err_rq_node and
err_sq_node entries remain on the CQs' err_state_qp_list.
Second, even for IB_QPT_RC QPs, xsc_ib_destroy_qp() purges err_state_qp_list
before destroy_qp_common() moves the hardware QP to XSC_QP_STATE_RST and
cleans the CQs under xsc_ib_lock_cqs(). While xsc_core_qp_modify() is
sleeping in destroy_qp_common(), a concurrent xsc_ib_poll_cq() on another CPU
can poll an error CQE for this QP and call xsc_ib_err_state_qp(), inserting
new nodes onto err_state_qp_list after this cleanup loop has already run, and
xsc_generate_flush_err_cqe() walks err_state_qp_list without holding cq->lock
while destroy_qp_kernel() frees the QP work queue buffers.
Should the err_state_qp_list removal happen for all kernel QP types inside
destroy_qp_common() while holding the CQ locks after the QP has been moved to
RESET?
[ ... ]
> +static inline void xsc_set_qp_access_flag(struct xsc_modify_qp_mbox_in *in,
> + struct xsc_qp_context *context, int access_flags)
> +{
> + in->hdr.ver = cpu_to_be16(XSC_QP_CONTEXT_V1);
> + context->qp_access_flags = 0;
[ ... ]
> +static int __xsc_ib_modify_qp(struct ib_qp *ibqp,
> + const struct ib_qp_attr *attr, int attr_mask,
> + enum ib_qp_state cur_state, enum ib_qp_state new_state,
> + const struct xsc_ib_modify_qp *ucmd,
> + struct ib_udata *udata)
> +{
> + struct xsc_ib_dev *dev = to_xdev(ibqp->device);
> + struct xsc_ib_qp *qp = to_xqp(ibqp);
> + struct xsc_ib_cq *send_cq, *recv_cq;
> + struct xsc_qp_context *context;
> + struct xsc_modify_qp_mbox_in *in;
> + struct xsc_qp_path path = {};
> + int err;
> + char buf[256] = {0};
> + char *ptr = buf;
> + int ret = 0;
> + struct xsc_core_device *xdev = dev->xdev;
> +
> + in = kzalloc_obj(*in, GFP_KERNEL);
> + if (!in)
> + return -ENOMEM;
> +
> + context = &qp->ctx;
[Severity: Medium]
In __xsc_ib_modify_qp(), context points directly to &qp->ctx and mutates the
cached context in place before xsc_set_path() or xsc_core_qp_modify()
succeeds, and transitioning to IB_QPS_RESET never clears stale fields in
qp->ctx. In addition, xsc_set_qp_access_flag() sets in->hdr.ver =
cpu_to_be16(XSC_QP_CONTEXT_V1) only when IB_QP_ACCESS_FLAGS is present in
attr_mask, so on subsequent transitions where IB_QP_ACCESS_FLAGS is omitted,
in->hdr.ver remains 0 even though the cached context->qp_access_flags is
copied into in->ctx.
Also, in create_qp_common(), in->req.page_shift is set to PAGE_SHIFT even
though create_user_qp() and create_kernel_qp() always compute hw_npages
(in->req.pa_num) and populate in->req.pas[] in 4 KB units (XSC_PAGE_SIZE_4K /
XSC_PAGE_SHIFT_4K). On architectures with 64 KB pages (PAGE_SHIFT == 16),
won't passing PAGE_SHIFT to firmware mismatch the 4 KB PAS entries?
[ ... ]
> + /*
> + * If we moved a kernel QP to RESET, clean up all old CQ
> + * entries and reinitialize the QP.
> + */
> + if (new_state == IB_QPS_RESET && !ibqp->uobject) {
> + get_cqs(qp->xqp.qp_type, qp->ibqp.send_cq, qp->ibqp.recv_cq, &send_cq, &recv_cq);
> + xsc_ib_cq_clean(recv_cq, qp->xqp.qpn);
> + if (send_cq != recv_cq)
> + xsc_ib_cq_clean(send_cq, qp->xqp.qpn);
> +
> + qp->rq.head = 0;
> + qp->rq.tail = 0;
> + qp->sq.head = 0;
> + qp->sq.tail = 0;
> + qp->sq.cur_post = 0;
> + qp->sq.last_poll = 0;
> + qp->sq.last_comp_ce_wqe = -1;
> + qp->sq.last_post_ce_wqe = -1;
> + qp->has_trig_cq_comp_handle = false;
> + }
[ ... ]
> + err = __xsc_ib_modify_qp(ibqp, attr, attr_mask, cur_state, new_state, &ucmd, udata);
> +
> + if (!err && (attr_mask & IB_QP_STATE) && !udata) {
> + bool trigger;
> +
> + get_cqs(qp->xqp.qp_type, qp->send_cq, qp->recv_cq, &send_cq, &recv_cq);
> + xsc_ib_lock_cqs(send_cq, recv_cq);
> + err = xsc_ib_err_state_qp(dev, qp, to_xsc_state(cur_state),
> + to_xsc_state(new_state), &trigger);
> + xsc_ib_unlock_cqs(send_cq, recv_cq);
> +
> + if (!err && trigger)
> + xsc_ib_trigger_cq_comp_handler(qp->send_cq, qp->recv_cq);
> + }
[Severity: High]
When a kernel QP enters error state via a hardware error CQE in
xsc_poll_one(), xsc_ib_err_state_qp() queues err_rq_node and err_sq_node onto
the CQs' err_state_qp_list and sets qp->has_trig_cq_comp_handle = true, but
qp->state remains at its previous software state (such as IB_QPS_RTS).
When xsc_ib_modify_qp() later resets the QP to IB_QPS_RESET, cur_state is
taken from qp->state (not IB_QPS_ERR), so the removal branch in
xsc_ib_err_state_qp() (cur_state == XSC_QP_STATE_ERR && state !=
XSC_QP_STATE_ERR) is not taken. Because __xsc_ib_modify_qp() also resets
qp->has_trig_cq_comp_handle = false, won't the existing err_rq_node and
err_sq_node be leaked on err_state_qp_list and duplicated if the QP enters
error state again?
Furthermore, in __xsc_ib_modify_qp(), qp->rq and qp->sq indices are zeroed
without holding cq->lock, qp->rq.lock, or qp->sq.lock before
xsc_ib_err_state_qp() removes the QP from err_state_qp_list, and
qp->rq.flush_wqe_cnt, qp->sq.flush_wqe_cnt, and qp->sq.need_flush[] are not
cleared. Can a concurrent xsc_ib_poll_cq() running
xsc_generate_flush_err_cqe() race with this reset?
> +
> + if (!err && !udata && (attr_mask & IB_QP_STATE) && ibqp->srq &&
> + cur_state == IB_QPS_RESET && new_state == IB_QPS_INIT) {
> + srq = to_xsrq(ibqp->srq);
> + spin_lock_irqsave(&srq->lock, flags);
> + srq->qp_num++;
> + wr_num = XSC_SRQ_MIN_WR_NUM;
> + ibdev_dbg(&dev->ib_dev,
> + "SRQ %d: qp %d to INIT, post %d srq wrs, qp_num %d\n",
> + srq->xsrq.srqn, qp->xqp.qpn, wr_num, srq->qp_num);
> + xsc_post_cache_wrs(srq, qp, wr_num, true);
> + spin_unlock_irqrestore(&srq->lock, flags);
> + }
[Severity: High]
Can kernel QPs attached to an SRQ leak cached WRs and corrupt srq->qp_num
across QP creation, reset, and destruction?
1. In create_qp_common(), a kernel QP with init_attr->srq is added to
xsrq->xsrq.srq_qp_list while still in IB_QPS_RESET and without
incrementing xsrq->qp_num. If xsc_ib_post_srq_recv() runs before the QP
transitions to IB_QPS_INIT, it sees the QP on srq_qp_list with
flush_wqe_cnt == 0 and calls xsc_post_cache_wrs(srq, qp, wr_num, false),
posting WQEs and ringing the RX doorbell on a RESET QP. When
xsc_ib_modify_qp() then transitions the QP from IB_QPS_RESET to
IB_QPS_INIT, xsc_post_cache_wrs(srq, qp, wr_num, true) overwrites
qp->srq_head_wr because init is true, leaking the previously attached
ib_recv_wr and sg_list allocations.
2. When a kernel QP with an SRQ is modified back to IB_QPS_RESET in
__xsc_ib_modify_qp(), qp->rq.head and qp->rq.tail are zeroed without
calling xsc_restore_cache_srq_wrs(), resetting qp->rq.flush_wqe_cnt, or
decrementing srq->qp_num. Transitioning from IB_QPS_RESET to IB_QPS_INIT
again increments srq->qp_num a second time and overwrites qp->srq_head_wr.
Conversely, if a QP is destroyed in IB_QPS_RESET before ever reaching
IB_QPS_INIT, xsc_ib_destroy_qp() decrements srq->qp_num even though it was
never incremented.
3. In xsc_ib_destroy_qp(), list_empty(&to_xsrq(qp->srq)->xsrq.srq_qp_list) is
checked outside xsrq->lock (which races with list_sort() in
xsc_ib_post_srq_recv()), and xsc_restore_cache_srq_wrs() is called before
destroy_qp_common() transitions the QP to RESET.
[ ... ]
> +int xsc_ib_query_qp(struct ib_qp *ibqp, struct ib_qp_attr *qp_attr, int qp_attr_mask,
> + struct ib_qp_init_attr *qp_init_attr)
> +{
> + struct xsc_ib_dev *dev = to_xdev(ibqp->device);
> + struct xsc_ib_qp *qp = to_xqp(ibqp);
> + struct xsc_query_qp_mbox_out *outb;
> + struct xsc_qp_context *context;
> + int err = 0;
> +
> + mutex_lock(&qp->mutex);
[Severity: High]
Does xsc_ib_query_qp() leak uninitialized kernel memory to userspace?
In drivers/infiniband/core/uverbs_cmd.c, ib_uverbs_query_qp() allocates attr
and init_attr with kmalloc_obj() (not kzalloc), and ib_query_qp() only zeroes
the two sgid_attr pointers before calling the driver's query_qp callback.
Because xsc_ib_query_qp() does not memset() qp_attr or qp_init_attr before
populating a subset of fields, uninitialized heap bytes in fields such as
path_mig_state, qkey, qp_access_flags, pkey_index, min_rnr_timer, timeout,
alt_port_num, alt_timeout, ah_attr (including 32 bytes of GRH/dgid data when
the uninitialized ah_flags byte has IB_AH_GRH set), and qp_init_attr are
copied back to userspace in struct ib_uverbs_query_qp_resp.
> +
> + outb = kzalloc_obj(*outb, GFP_KERNEL);
> + if (!outb) {
> + err = -ENOMEM;
> + goto out;
> + }
> + context = &outb->ctx;
> + err = xsc_core_qp_query(dev->xdev, &qp->xqp, outb, sizeof(*outb));
> + if (err)
> + goto out_free;
> +
> + if (qp->xqp.err_occurred) {
> + qp->state = IB_QPS_ERR;
> + qp->xqp.err_occurred = 0;
> + }
> + qp_attr->qp_state = qp->state;
> + qp_attr->path_mtu = context->mtu_mode ? IB_MTU_4096 : IB_MTU_1024;
> + qp_attr->rq_psn = be32_to_cpu(context->next_recv_psn) & 0xffffff;
> + qp_attr->sq_psn = be32_to_cpu(context->next_send_psn) & 0xffffff;
> + qp_attr->dest_qp_num = be32_to_cpu(context->remote_qpn) & 0xffffff;
> + qp_attr->sq_draining = 0;
> + qp_attr->retry_cnt = context->retry_cnt;
> + qp_attr->rnr_retry = context->rnr_retry;
> + qp_attr->cur_qp_state = qp_attr->qp_state;
> + qp_attr->cap.max_recv_wr = qp->rq.wqe_cnt;
> + qp_attr->cap.max_recv_sge = qp->rq.max_gs;
> + qp_attr->max_rd_atomic = context->max_sra;
> + qp_attr->max_dest_rd_atomic = context->max_rra;
> + qp_attr->port_num = 1;
> +
> + if (!ibqp->uobject) {
> + qp_attr->cap.max_send_wr = qp->sq.wqe_cnt;
> + qp_attr->cap.max_send_sge = qp->sq.max_gs;
> + } else {
> + qp_attr->cap.max_send_wr = qp->sq.wqe_cnt;
> + qp_attr->cap.max_send_sge = qp->sq.max_gs;
> + }
[Severity: Medium]
In __xsc_ib_modify_qp(), context->mtu_mode is set to 0 for IB_MTU_1024, 1 for
IB_MTU_4096, and 2 for IB_MTU_8192, but xsc_ib_query_qp() decodes
context->mtu_mode ? IB_MTU_4096 : IB_MTU_1024, reporting IB_MTU_4096 when
mtu_mode is 2. Should xsc_ib_query_qp() map mtu_mode == 2 to IB_MTU_8192?
Also, both branches of if (!ibqp->uobject) in xsc_ib_query_qp() are
identical, and the static helpers to_ib_qp_state() and to_ib_mig_state()
defined just above xsc_ib_query_qp() are unused.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261104155650.37053-1-tianx@yunsilicon.com?part=13
next prev parent reply other threads:[~2026-11-05 16:09 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-04 15:57 [PATCH v1 00/15] Yunsilicon XSC ethernet and RDMA drivers Xin Tian
2026-11-04 15:56 ` [PATCH v1 01/15] MAINTAINERS: add the Yunsilicon XSC drivers Xin Tian
2026-11-04 15:56 ` [PATCH v1 02/15] net/xsc: add PCI core driver skeleton Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:56 ` [PATCH v1 03/15] net/xsc: add the register HAL Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 04/15] net/xsc: add the command queue Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 05/15] net/xsc: add event queues, interrupts and the Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 06/15] net/xsc: add the CQ and QP resource pools Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 07/15] net/xsc: add ethernet netdev registration Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 08/15] net/xsc: add ethernet TX path Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 09/15] net/xsc: add ethernet RX path and statistics Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 10/15] RDMA/xsc: add RDMA device support Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 11/15] RDMA/xsc: add RDMA PD/MR Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 12/15] RDMA/xsc: add RDMA CQ/SRQ/AH Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 13/15] RDMA/xsc: add QP verbs Xin Tian
2026-11-05 16:09 ` sashiko-bot [this message]
2026-11-04 15:57 ` [PATCH v1 14/15] RDMA/xsc: add the RDMA send path Xin Tian
2026-11-05 16:09 ` sashiko-bot
2026-11-04 15:57 ` [PATCH v1 15/15] RDMA/xsc: add RDMA counters and sysfs Xin Tian
2026-11-05 16:09 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-199435@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=linux-rdma@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=tianx@yunsilicon.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox