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: 32+ 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-07 21:03 ` Andrew Lunn
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
2026-11-10 18:48 ` [PATCH v1 00/15] Yunsilicon XSC ethernet and RDMA drivers Jakub Kicinski
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.