From: sashiko-bot@kernel.org
To: "Doug Ledford" <doug.ledford@hpe.com>,
"Slingshot Open Source Team"
<slingshot-os-team@groups.ext.hpe.com>,
"Nathan Chancellor" <nathan@kernel.org>,
"Nicolas Schier" <nsc@kernel.org>,
"Richard Cochran" <richardcochran@gmail.com>,
"Andrew Lunn" <andrew+netdev@lunn.ch>,
"David S. Miller" <davem@davemloft.net>,
"Eric Dumazet" <edumazet@kernel.org>,
"Jakub Kicinski" <kuba@kernel.org>,
"Paolo Abeni" <pabeni@redhat.com>,
"Sumit Semwal" <sumit.semwal@linaro.org>,
"Christian König" <christian.koenig@amd.com>,
"Guenter Roeck" <linux@roeck-us.net>
Cc: linux-kbuild@vger.kernel.org
Subject: Re: [PATCH 19/23] net: hpe: ss1: add SR-IOV support
Date: Sat, 31 Oct 2026 21:02:17 +0000 [thread overview]
Message-ID: <sashiko-outbox-192146@kernel.org> (raw)
In-Reply-To: <20261030-cxi-netdev-submission-v1-19-8c2c391cc283@hpe.com>
Thank you for your contribution! Sashiko AI review found 16 potential issue(s) to consider:
- [Critical] net: hpe: ss1: NULL deref and guest cred bypass in pf_get_kvm_task()
- [Critical] net: hpe: ss1: unauthenticated VF vsock handshake and global port binding
- [Critical] net: hpe: ss1: kthread_stop() UAF on self-exiting SR-IOV kthreads
- [Critical] net: hpe: ss1: premature unlocked vf->req_sock clear in pf_vf_msghandler()
- [Critical] net: hpe: ss1: uncalled __cxi_vf_structure_offset_checks() hides layout bugs
- [High] net: hpe: ss1: #warning under CXI_DISABLE_SRIOV breaks CONFIG_WERROR builds
- [High] net: hpe: ss1: unlocked driver and vfio_dev->kvm access in pf_get_kvm_task()
- [High] net: hpe: ss1: missing num_vfs bounds check against C_NUM_VFS in enable_sriov()
- [High] net: hpe: ss1: enable_sriov() enables VFs before listener sockets are bound
- [High] net: hpe: ss1: vf_notif_handler() hardcodes seq 0 and breaks notifications
- [High] net: hpe: ss1: missing vsock sk_rcvtimeo and infinite -EINTR retry loops
- [High] net: hpe: ss1: broken VF IRQ binary search and num_vfs == 1 check in handshake
- [High] net: hpe: ss1: unvalidated vsock message lengths and uninitialized stack vars
- [High] net: hpe: ss1: pf_vf_msghandler() sends stale MAX_VFMSG_SIZE reply on error
- [High] net: hpe: ss1: uninitialized vf_cmd_lock when with_vf_support is false
- [Medium] net: hpe: ss1: enable_sriov() error return, offset order, and #warning build
--
commit cd0507e8307b6aff215ba76c73aa7b6a9f1e28d5
Author: Doug Ledford <doug.ledford@hpe.com>
net: hpe: ss1: add SR-IOV support
This commit adds SR-IOV support for the HPE Cassini driver, including VF
creation, a vsock-based PF/VF mailbox and notification mechanism, and
compile-time structure offset checks for VF resource structures.
> diff --git a/drivers/net/ethernet/hpe/ss1/cass_sriov.c b/drivers/net/ethernet/hpe/ss1/cass_sriov.c
> new file mode 100644
> index 0000000000000..5c947372ab9c7
> --- /dev/null
> +++ b/drivers/net/ethernet/hpe/ss1/cass_sriov.c
> @@ -0,0 +1,1274 @@
[ ... ]
> +#if defined(CXI_DISABLE_SRIOV)
> +#warning "SR-IOV support is disabled."
> +
> +int cass_sriov_configure(struct pci_dev *pdev, int num_vfs)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +int cass_vf_init(struct cass_dev *hw)
> +{
> + return -EOPNOTSUPP;
> +}
[Severity: High]
In cass_core.h, CXI_DISABLE_SRIOV is defined whenever CONFIG_KVM_VFIO is
not enabled, and CONFIG_HPE_SS1 does not depend on or select
CONFIG_KVM_VFIO.
When building with CONFIG_KVM_VFIO=n and CONFIG_WERROR=y, will this
unconditional #warning cause the build to fail with -Werror=cpp?
Also, since only pf_get_kvm_task() on the PF side touches vfio_device->kvm,
does gating the entire file on CXI_DISABLE_SRIOV prevent a guest VM kernel
built without host CONFIG_KVM_VFIO from probing a Cassini VF via
cass_vf_init() and cxi_send_msg_to_pf()?
[ ... ]
> +/* Given a PF's pci_dev struct, find the pci_dev of one of its VFs */
> +static struct pci_dev *get_vf_pdev(struct pci_dev *pdev, int vf_idx)
> +{
> + int devfn = pci_iov_virtfn_devfn(pdev, vf_idx);
> +
> + if (devfn < 0)
> + return ERR_PTR(devfn);
> + return pci_get_slot(pdev->bus, devfn);
> +}
> +
> +/* Identify the KVM task associated with a VF that is bound to a guest VM. */
> +static struct task_struct *pf_get_kvm_task(struct cass_vf *vf)
> +{
> + struct cass_dev *hw = vf->hw;
> + struct pci_dev *vf_pdev = NULL;
> + struct vfio_device *vfio_dev = NULL;
> + struct task_struct *task = NULL;
> + struct pid *pid;
> +
> + vf_pdev = get_vf_pdev(hw->cdev.pdev, vf->vf_idx);
> + if (IS_ERR(vf_pdev)) {
> + cxidev_err(&hw->cdev, "vf %d: could not get device", vf->vf_idx);
> + return ERR_PTR(PTR_ERR(vf_pdev));
> + }
> +
> + cxidev_dbg(&hw->cdev, "vf %d: device at %s", vf->vf_idx, pci_name(vf_pdev));
> +
> + if (!vf_pdev->driver) {
> + task = ERR_PTR(-ENODEV);
> + } else if (!strncmp(vf_pdev->driver->name, "vfio-pci", strlen("vfio-pci"))) {
> + /* VF is under control of VFIO driver, look for KVM task. */
> + vfio_dev = pci_get_drvdata(vf_pdev);
> + if (vfio_dev->kvm) {
> + pid = find_get_pid(vfio_dev->kvm->userspace_pid);
> + task = get_pid_task(pid, PIDTYPE_PID);
> + put_pid(pid);
> + } else {
> + task = ERR_PTR(-ESRCH);
> + }
[Severity: Critical]
Could pf_get_kvm_task() trigger a NULL pointer dereference or allow a guest
VM to bypass host credential checks?
First, get_vf_pdev() calls pci_get_slot(pdev->bus, devfn), which only
searches pdev->bus (missing VFs placed on a virtual bus where
pci_iov_virtfn_bus(pdev, vf_idx) != pdev->bus->number, or when the VF
pci_dev has not been created yet or was removed via sysfs) and returns NULL
when no device is found. Since pf_get_kvm_task() only checks
IS_ERR(vf_pdev), a NULL return is dereferenced by pci_name(vf_pdev) and
vf_pdev->driver.
Second, when vf_pdev is bound to vfio-pci and vfio_dev->kvm is set,
get_pid_task(pid, PIDTYPE_PID) can return NULL if the thread that originally
called KVM_CREATE_VM (vfio_dev->kvm->userspace_pid) has exited while other
VMM threads keep the VM alive. In that case pf_get_kvm_task() returns NULL
instead of an error pointer, and pf_vf_msghandler() treats vf->kvm_task ==
NULL as a host-owned VF:
if (vf->kvm_task) {
...
uid = __kuid_val(cred->uid);
gid = __kgid_val(cred->gid);
rcu_read_unlock();
}
Wouldn't that pass the untrusted guest-supplied uid and gid from the vsock
header directly to hw->msg_relay()?
> + } else if (!strncmp(vf_pdev->driver->name, KBUILD_MODNAME,
> + strlen(KBUILD_MODNAME))) {
> + /* VF is under control of our driver, KVM task is not applicable */
> + task = NULL;
> + } else {
> + task = ERR_PTR(-EPERM);
> + }
> + pci_dev_put(vf_pdev);
> + return task;
> +}
[Severity: High]
What protects vf_pdev->driver, pci_get_drvdata(vf_pdev), and vfio_dev->kvm
against concurrent driver unbind or VFIO device close in pf_get_kvm_task()?
Without holding device_lock(&vf_pdev->dev) or vfio_dev->dev_set->lock, a
concurrent sysfs unbind (device_release_driver_internal() ->
vfio_pci_remove()) can clear vf_pdev->driver and free vfio_dev after the
!vf_pdev->driver check, or vfio_device_put_kvm() can clear and free
vfio_dev->kvm while pf_get_kvm_task() dereferences
vfio_dev->kvm->userspace_pid.
> +
> +/* Handler thread for incoming messages from the VF driver to the PF. 1 instance
> + * per active VF.
> + */
> +static int pf_vf_msghandler(void *data)
> +{
[ ... ]
> + mutex_lock(&hw->msg_relay_lock);
> + if (hw->msg_relay) {
> + reply_len = MAX_VFMSG_SIZE;
> + msg_rc = hw->msg_relay(hw->msg_relay_data, vf->vf_idx,
> + vf->request, request_len, uid, gid,
> + vf->reply, &reply_len);
> + } else {
> + reply_len = 0;
> + msg_rc = -ENODEV;
> + }
> + mutex_unlock(&hw->msg_relay_lock);
> +
> + if (reply_len > MAX_VFMSG_SIZE) {
> + reply_len = 0;
> + msg_rc = -E2BIG;
> + }
> +
> + cxidev_dbg(&hw->cdev, "vf %d: responding with %ld bytes, rc=%d",
> + vf->vf_idx, reply_len, msg_rc);
> + rc = write_message_to_vsock(vf->req_sock, vf->reply, reply_len,
> + msg_rc, seq);
[Severity: High]
In pf_vf_msghandler(), reply_len is initialized to MAX_VFMSG_SIZE before
calling hw->msg_relay(). If hw->msg_relay() returns a negative error code
without setting *reply_len = 0, won't write_message_to_vsock() send
MAX_VFMSG_SIZE (22,452) bytes of stale data from vf->reply to the VF, both
leaking PF memory and causing read_message_from_vsock() on the VF to fail
with -EMSGSIZE and shut down the socket?
[ ... ]
> +err:
> + cxidev_dbg(&hw->cdev, "vf %d: handler exiting, rc=%d", vf->vf_idx, rc);
> +
> + if (vf->kvm_task)
> + put_task_struct(vf->kvm_task);
> + kernel_sock_shutdown(vf->req_sock, SHUT_RDWR);
> + sock_release(vf->req_sock);
> + vf->req_sock = NULL;
> + mutex_lock(&vf->notif_lock);
> + if (vf->notif_sock) {
> + kernel_sock_shutdown(vf->notif_sock, SHUT_RDWR);
> + sock_release(vf->notif_sock);
> + vf->notif_sock = NULL;
> + }
> + mutex_unlock(&vf->notif_lock);
> +
> + vf->kvm_task = NULL;
> +
> + cass_ac_phys_free(hw, vf->phys_ac);
> + vf->phys_ac = 0;
> +
> + return rc;
> +}
[Severity: Critical]
Is it safe for pf_vf_msghandler() to clear vf->req_sock = NULL without
locking before releasing vf->notif_sock, clearing vf->kvm_task, and calling
cass_ac_phys_free(hw, vf->phys_ac)?
Other paths treat vf->req_sock != NULL as the indicator that a VF slot is
active:
- In pf_vf_listener(), if vf->req_sock becomes NULL right as SR-IOV is being
disabled in cass_remove() -> disable_sriov(), pf_vf_listener() skips
kthread_stop(hw->vfs[vf_idx].task), allowing disable_sriov() and
cass_remove() to free hw while pf_vf_msghandler() is still running
cass_ac_phys_free(hw, vf->phys_ac).
- In handle_vf_req_conn(), a reconnecting VF can see vf->req_sock == NULL
and allocate a new vf->phys_ac and vf->kvm_task, which the exiting
pf_vf_msghandler() thread then overwrites and frees.
- In handle_vf_notif_conn(), checking hw->vfs[i].req_sock without holding
vf->notif_lock can race with this teardown and install vf->notif_sock
after the notif_lock cleanup block has already run.
> +
> +/* Probe inactive VFs from range_min to range_max inclusive */
> +static int pf_probe_vfs(struct cass_dev *hw, struct socket *sock,
> + int range_min, int range_max)
> +{
> + int i, rc;
> + int magic;
> + size_t msg_len = sizeof(magic);
> + union c_pi_ipd_cfg_pf_vf_irq irqs = {
> + .irq = 0,
> + };
> +
> + for (i = range_min; i <= range_max; i++)
> + if (!hw->vfs[i].req_sock) /* Only probe VFs that aren't already active */
> + irqs.irq |= 1ULL << i;
> + if (!irqs.irq)
> + return 0;
> +
> + magic = CXI_SRIOV_MAGIC1;
> + rc = write_message_to_vsock(sock, &magic, sizeof(magic), 0, 0);
> + if (rc < 0) {
> + cxidev_err(&hw->cdev, "could not send magic to incoming VF: %d", rc);
> + return rc;
> + }
> +
> + cass_write(hw, C_PI_IPD_CFG_PF_VF_IRQ, &irqs,
> + sizeof(union c_pi_ipd_cfg_pf_vf_irq));
> +
> + msg_len = sizeof(magic);
> + do {
> + rc = read_message_from_vsock(sock, &magic, &msg_len, NULL, NULL, NULL, NULL);
> + if (rc == -EINTR)
> + schedule();
> + } while (rc == -EINTR);
> + if (rc < 0) {
> + cxidev_err(&hw->cdev, "could not read magic from incoming VF: %d", rc);
> + return rc;
> + }
> +
> + return magic;
> +}
> +
> +/* Identify which VF an incoming connection belongs to, by probing the PF-to-VF
> + * interrupt of VFs that do not have an active connection.
> + */
> +static int pf_vf_handshake(struct cass_dev *hw, struct socket *sock)
> +{
> + int range_min = 0;
> + int range_max = hw->num_vfs - 1;
> + int range_mid, rc, seq, vf_idx;
> + int magic;
> +
> + seq = 0; /* Handshake sequence number */
> + do {
> + magic = CXI_SRIOV_MAGIC1 + seq;
> + range_mid = range_min + (range_max - range_min) / 2;
> + if (pf_probe_vfs(hw, sock, range_min, range_mid) == magic) {
> + seq += 1;
> + range_max = range_mid;
> + } else if (pf_probe_vfs(hw, sock, range_mid + 1, range_max) == magic) {
> + seq += 1;
> + range_min = range_mid + 1;
> + } else {
> + break;
> + }
> + } while (range_max != range_min);
> +
> + if (range_max != range_min) {
> + cxidev_err(&hw->cdev, "vf search failed");
> + return -ENOENT;
> + }
> + vf_idx = range_min;
[Severity: High]
How does the binary search in pf_vf_handshake() work when a VF in the upper
half [range_mid + 1, range_max] connects while at least one VF in the lower
half [range_min, range_mid] is still inactive?
1. pf_vf_handshake() first calls pf_probe_vfs(hw, sock, range_min,
range_mid), which sends CXI_SRIOV_MAGIC1 over sock to the connecting
upper-half VF, raises C_PI_IPD_CFG_PF_VF_IRQ only for VFs in
[range_min, range_mid], and blocks waiting for a reply on sock.
2. In cass_vf_init(), the connecting upper-half VF receives
CXI_SRIOV_MAGIC1 and enters wait_for_completion_timeout(&hw->pf_to_vf_comp,
CXI_SRIOV_VF_TIMEOUT), waiting for an interrupt that was only sent to the
lower half, and aborts after 10 seconds with -ETIMEDOUT.
3. Meanwhile, any other inactive VFs in [range_min, range_mid] that have
already registered pf_to_vf_int_cb() will have hw->pf_to_vf_comp
completed prematurely by the probe interrupt.
4. Also, when hw->num_vfs == 1, both range_min and range_max start at 0; if
pf_probe_vfs() fails and breaks out of the loop, range_max != range_min
is false (0 != 0), so doesn't pf_vf_handshake() still succeed and return
vf_idx = 0?
[ ... ]
> +/* Handle an incoming request connection from a VF */
> +static void handle_vf_req_conn(struct cass_dev *hw,
> + struct socket *incoming)
> +{
> + struct sockaddr_vm peeraddr;
> + struct cass_vf *vf;
> + int rc, vf_idx;
> +
> + rc = kernel_getpeername(incoming, (struct sockaddr *)&peeraddr);
> + if (rc < 0) {
> + cxidev_err(&hw->cdev, "could not get peer addr for vf: %d", rc);
> + goto close_sock;
> + }
> +
> + vf_idx = pf_vf_handshake(hw, incoming);
[Severity: Critical]
What prevents an unprivileged local host user (via vsock_loopback) or an
unauthorized guest VM from claiming a VF in pf_vf_handshake()?
In enable_sriov(), vsock_loopback is loaded and pf_vf_listener() binds
CXI_SRIOV_VSOCK_PORT and CXI_SRIOV_VSOCK_NOTIF_PORT to VMADDR_CID_ANY in
init_net. In pf_probe_vfs(), the PF prompts the peer with CXI_SRIOV_MAGIC1
and accepts the predictable value CXI_SRIOV_MAGIC1 + seq in reply without
verifying that the peer actually received the hardware interrupt or
validating peeraddr.svm_cid in handle_vf_req_conn(). If the VF is bound to
the host driver (so vf->kvm_task is NULL), a local process can claim the VF
slot and supply arbitrary uid and gid values (such as 0) in the vsock
message header.
Also, since pf_vf_listener() binds fixed global ports CXI_SRIOV_VSOCK_PORT
(0x17db) and CXI_SRIOV_VSOCK_NOTIF_PORT (0x17dc) with VMADDR_CID_ANY, won't
enabling SR-IOV on a second Cassini PF in the same system fail in
vsock_create_listen() -> kernel_bind() with -EADDRINUSE?
[ ... ]
> +/* Handle an incoming notification connection from a VF */
> +static void handle_vf_notif_conn(struct cass_dev *hw,
> + struct socket *incoming)
> +{
> + struct sockaddr_vm peeraddr;
> + struct cass_vf *vf;
> + int rc, vf_idx, i;
> + unsigned int token;
> + struct cass_vf_notif_ping ping = {
> + .op = CASS_VF_NOTIF_OP_PING,
> + };
> + size_t msg_len;
> +
> + rc = kernel_getpeername(incoming, (struct sockaddr *)&peeraddr);
> + if (rc < 0) {
> + cxidev_err(&hw->cdev,
> + "could not get peer addr for notification connection: %d", rc);
> + goto close_sock;
> + }
> +
> + /* Read token to identify which VF this is */
> + msg_len = sizeof(token);
> + do {
> + rc = read_message_from_vsock(incoming, &token, &msg_len, NULL, NULL, NULL, NULL);
> + if (rc == -EINTR)
> + schedule();
> + } while (rc == -EINTR);
[ ... ]
> + vf->notif_seq = 0;
> + vf->notif_sock = incoming;
> + mutex_unlock(&vf->notif_lock);
> + cxidev_dbg(&hw->cdev, "vf %d: notification socket connected", vf_idx);
> +
> + /* Send test notification */
> + msg_len = sizeof(ping);
> + rc = cxi_send_msg_to_vf(&hw->cdev, vf_idx, &ping, msg_len,
> + NULL, NULL);
[Severity: High]
Can an unresponsive peer hang pf_vf_listener() or deadlock vf->notif_lock,
and can the -EINTR retry loops spin indefinitely?
1. Accepted AF_VSOCK sockets created by kernel_accept() initialize
sk->sk_rcvtimeo to MAX_SCHEDULE_TIMEOUT rather than inheriting the
listener socket's timeout. In handle_vf_req_conn(),
incoming->sk->sk_rcvtimeo is not set before pf_vf_handshake() (it is only
set later in pf_vf_msghandler()), and in handle_vf_notif_conn(),
sk_rcvtimeo is never set on incoming / vf->notif_sock at all. If a peer
connects and sends nothing, pf_vf_listener() blocks forever; similarly,
if a VF stops responding to notifications, cxi_send_msg_to_vf() blocks
forever in cxi_vsock_send() while holding vf->notif_lock, deadlocking
teardown in pf_vf_msghandler() and pf_vf_listener().
2. In pf_probe_vfs(), handle_vf_notif_conn(), and cass_vf_init(), the loops:
do {
rc = read_message_from_vsock(...);
if (rc == -EINTR)
schedule();
} while (rc == -EINTR);
do not check kthread_should_stop() or signal_pending(). When a signal or
kthread_stop() is pending, kernel_recvmsg() immediately returns -EINTR
and schedule() with state TASK_RUNNING returns without sleeping, causing
a tight infinite loop.
[ ... ]
> +static int enable_sriov(struct pci_dev *pdev, int num_vfs)
> +{
> + int rc;
> + int sriov;
> + u16 offset;
> + u16 stride;
> + union c_pi_cfg_pri_sriov pri_sriov = {};
> + struct cass_dev *hw = pci_get_drvdata(pdev);
> +
> + rc = request_module("vsock");
> + if (rc) {
> + cxidev_err(&hw->cdev, "could not load vsock module");
> + goto err_novf;
> + }
> +
> + rc = request_module("vsock_loopback");
> + if (rc) {
> + cxidev_err(&hw->cdev, "could not load vsock_loopback module");
> + goto err_novf;
> + }
> +
> + hw->num_vfs = num_vfs;
> +
> + if (!hw->vf_listener)
> + hw->vf_listener = kthread_run(pf_vf_listener, hw, "cxi_vf_listener");
> + if (IS_ERR(hw->vf_listener)) {
> + cxidev_err(&hw->cdev, "could not start vf listener thread");
> + rc = PTR_ERR(hw->vf_listener);
> + hw->vf_listener = NULL;
> + goto err_novf;
> + }
> +
> + /* The VF Offset and Stride need to match the SR-IOV configuration. */
> + sriov = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_SRIOV);
> + if (!sriov) {
> + cxidev_err(&hw->cdev, "No extended capabilities found\n");
> + rc = -ENODEV;
> + goto err_kill_listener;
> + }
> +
> + pci_read_config_word(pdev, sriov + PCI_SRIOV_VF_OFFSET, &offset);
> + pci_read_config_word(pdev, sriov + PCI_SRIOV_VF_STRIDE, &stride);
> +
> + pri_sriov.vf_offset = offset;
> + pri_sriov.vf_stride = stride;
> +
> + cass_write(hw, C_PI_CFG_PRI_SRIOV, &pri_sriov,
> + sizeof(union c_pi_cfg_pri_sriov));
> +
> + rc = pci_enable_sriov(pdev, num_vfs);
> + if (rc) {
> + cxidev_err(&hw->cdev, "SRIOV enable failed %d\n", rc);
> + goto err_kill_listener;
> + }
> +
> + return num_vfs;
> +
> +err_kill_listener:
> + kthread_stop(hw->vf_listener);
> + hw->vf_listener = NULL;
> +err_novf:
> + hw->num_vfs = 0;
> + return rc;
> +}
[Severity: High]
Does enable_sriov() race with pf_vf_listener() when creating the listening
sockets?
enable_sriov() starts pf_vf_listener() asynchronously via kthread_run() and
immediately calls pci_enable_sriov(pdev, num_vfs) without waiting for
pf_vf_listener() to finish calling vsock_create_listen() for hw->vf_req_sock
and hw->vf_notif_sock. When sriov_drivers_autoprobe is enabled on the host,
pci_enable_sriov() synchronously probes VFs (cass_probe() -> cass_vf_init()
-> kernel_connect()), which can run before pf_vf_listener() has bound
CXI_SRIOV_VSOCK_PORT. Also, if vsock_create_listen() fails in
pf_vf_listener(), enable_sriov() will still enable SR-IOV and return
num_vfs.
[Severity: Medium]
Are there also a couple of error-handling and ordering issues in
enable_sriov()?
1. When modprobe fails, request_module() returns a positive wait status
(such as 256). Jumping to err_novf returns that positive rc from
cass_sriov_configure() to sriov_numvfs_store(), which only treats negative
values as errors.
2. PCI_SRIOV_VF_OFFSET and PCI_SRIOV_VF_STRIDE are read and written to
C_PI_CFG_PRI_SRIOV before pci_enable_sriov() writes PCI_SRIOV_NUM_VF
(which under the PCIe SR-IOV specification can update First VF Offset and
VF Stride).
> +
> +int cass_sriov_configure(struct pci_dev *pdev, int num_vfs)
> +{
> + if (num_vfs < 0)
> + return -EINVAL;
> +
> + if (num_vfs == 0) {
> + disable_sriov(pdev);
> + return 0;
> + }
> +
> + return enable_sriov(pdev, num_vfs);
> +}
[Severity: High]
Should cass_sriov_configure() or enable_sriov() validate that num_vfs does
not exceed C_NUM_VFS (64) before setting hw->num_vfs = num_vfs?
In struct cass_dev, vfs[] has fixed size C_NUM_VFS (64), and pf_probe_vfs()
evaluates 1ULL << i for i up to hw->num_vfs - 1. If num_vfs > C_NUM_VFS,
pf_vf_listener(), pf_probe_vfs(), handle_vf_req_conn(),
handle_vf_notif_conn(), cxi_send_msg_to_vf(), and cass_vf_get_token() can
shift past 63 bits and index hw->vfs[] out of bounds.
[ ... ]
> +/* VF-side handler thread for receiving notifications from PF */
> +static int vf_notif_handler(void *data)
> +{
> + struct cass_dev *hw = (struct cass_dev *)data;
> + int rc;
> + size_t msg_len, rsp_len;
> +
> + cxidev_dbg(&hw->cdev, "started notification handler");
> +
> + while (!kthread_should_stop()) {
> + msg_len = MAX_VFMSG_SIZE;
> + rc = read_message_from_vsock(hw->vf_notif_sock, hw->vf_notif_buf,
> + &msg_len, NULL, NULL, NULL, NULL);
[ ... ]
> + rsp_len = 0;
> + rc = dispatch_vf_notif(hw, hw->vf_notif_buf, msg_len,
> + hw->vf_notif_rsp_buf, &rsp_len);
> + rc = write_message_to_vsock(hw->vf_notif_sock, hw->vf_notif_rsp_buf,
> + rsp_len, rc, 0);
> + if (rc < 0) {
> + cxidev_err(&hw->cdev, "failed to send notification response: %d", rc);
> + break;
> + }
> + }
[Severity: High]
In vf_notif_handler(), read_message_from_vsock() passes NULL for seq and
write_message_to_vsock() hardcodes seq = 0 in the reply.
Since cxi_send_msg_to_vf() sends notifications via cxi_vsock_send() with
hw->vfs[vf_num].notif_seq++, and cxi_vsock_send() checks:
if (rsp_seq != seq) {
kernel_sock_shutdown(sock, SHUT_RDWR);
cxidev_err(&hw->cdev, "expected response seq %d but got %d",
seq, rsp_seq);
return -EPROTO;
}
won't every notification after the initial ping (where notif_seq is 0) fail
with -EPROTO and shut down the notification socket?
[ ... ]
> +int cass_vf_init(struct cass_dev *hw)
> +{
[ ... ]
> + if (!hw->with_vf_support)
> + return 0;
> +
> + init_completion(&hw->pf_to_vf_comp);
> +
> + mutex_init(&hw->vf_cmd_lock);
[Severity: High]
If hw->with_vf_support is false on a VF, cass_vf_init() returns 0 before
mutex_init(&hw->vf_cmd_lock). When cass_probe() subsequently calls
cxi_send_msg_to_pf(), won't mutex_lock(&hw->vf_cmd_lock) operate on an
uninitialized mutex?
[ ... ]
> +void cass_vf_fini(struct cass_dev *hw)
> +{
> + if (!hw->with_vf_support)
> + return;
> +
> + free_irq(hw->pf_vf_vec, hw);
> +
> + if (hw->vf_notif_handler) {
> + kthread_stop(hw->vf_notif_handler);
> + hw->vf_notif_handler = NULL;
> + }
[Severity: Critical]
Can kthread_stop() be called on a task_struct that has already exited and
been freed in cass_vf_fini(), disable_sriov(), enable_sriov(), and
pf_vf_listener()?
kthread_run() does not take an extra reference on task_struct for the
caller, so if a kthread returns before kthread_stop() is called, its
task_struct is freed on exit:
- In cass_vf_fini(), if the PF already closed the notification socket (or a
socket error occurred), vf_notif_handler() breaks out of its loop and
returns, leaving hw->vf_notif_handler pointing to a freed task_struct when
kthread_stop(hw->vf_notif_handler) is called.
- In pf_vf_listener(), if vsock_create_listen() or kernel_accept() returns an
error, pf_vf_listener() exits immediately, after which enable_sriov() or
disable_sriov() calls kthread_stop(hw->vf_listener).
- In pf_vf_msghandler(), the thread exits whenever a VF disconnects or
pf_get_kvm_task() fails (and disable_sriov() calls pci_disable_sriov()
before stopping hw->vf_listener), which can race with pf_vf_listener()
calling kthread_stop(hw->vfs[vf_idx].task).
[ ... ]
> diff --git a/drivers/net/ethernet/hpe/ss1/cass_vf.c b/drivers/net/ethernet/hpe/ss1/cass_vf.c
> new file mode 100644
> index 0000000000000..fde40d1b491ae
> --- /dev/null
> +++ b/drivers/net/ethernet/hpe/ss1/cass_vf.c
> @@ -0,0 +1,58 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright 2026 Hewlett Packard Enterprise Development LP */
> +
> +#include <linux/hpe/cxi/cxi.h>
> +#include <linux/dma-mapping.h>
> +#include "cass_core.h"
> +#include "cass_vf.h"
> +
> +/* Compile-time assertions to ensure VF structures can be safely cast to their
> + * corresponding PF structures and vice versa.
> + * These verify that the common fields are at the same offsets in both structures.
> + */
> +static inline void __cxi_vf_structure_offset_checks(void)
> +{
> + /* Ensure cxi_lni_priv_vf can be cast to cxi_lni_priv */
> + BUILD_BUG_ON(offsetof(struct cxi_lni_priv_vf, dev) !=
> + offsetof(struct cxi_lni_priv, dev));
> + BUILD_BUG_ON(offsetof(struct cxi_lni_priv_vf, lni) !=
> + offsetof(struct cxi_lni_priv, lni));
[ ... ]
> + /* Ensure cxi_md_priv_vf can be cast to cxi_md_priv */
> + BUILD_BUG_ON(offsetof(struct cxi_md_priv_vf, lni_priv) !=
> + offsetof(struct cxi_md_priv, lni_priv));
> + BUILD_BUG_ON(offsetof(struct cxi_md_priv_vf, device) !=
> + offsetof(struct cxi_md_priv, device));
> + BUILD_BUG_ON(offsetof(struct cxi_md_priv_vf, md) !=
> + offsetof(struct cxi_md_priv, md));
> + BUILD_BUG_ON(offsetof(struct cxi_md_priv_vf, sgt) !=
> + offsetof(struct cxi_md_priv, sgt));
> + BUILD_BUG_ON(offsetof(struct cxi_md_priv_vf, pages) !=
> + offsetof(struct cxi_md_priv, pages));
> + BUILD_BUG_ON(offsetof(struct cxi_md_priv_vf, flags) !=
> + offsetof(struct cxi_md_priv, flags));
> +}
[Severity: Critical]
Because __cxi_vf_structure_offset_checks() is declared static inline and has
no callers, the compiler discards the function without evaluating any of the
BUILD_BUG_ON() assertions (unlike static_assert() at file scope).
If these checks are evaluated, three of them fail due to layout mismatches
between cass_vf.h and cxi_core.h:
1. struct cxi_lni_priv in cxi_core.h has
DECLARE_BITMAP(leased_pids, 1 << MAX_PID_BITS) (64 bytes) between dev and
lni, whereas struct cxi_lni_priv_vf in cass_vf.h places lni immediately
after dev. When VF code such as cxi_map_sgtable_vf() or
cxi_domain_alloc_vf() runs container_of(lni, struct cxi_lni_priv, lni) on
a struct cxi_lni_priv_vf allocation, won't the resulting pointer point 64
bytes before the allocated object?
2. In struct cxi_md_priv (cxi_core.h), pages precedes sgt, whereas in
struct cxi_md_priv_vf (cass_vf.h), sgt precedes pages, swapping the two
pointers when casting between the two structures.
> diff --git a/drivers/net/ethernet/hpe/ss1/cass_vf_notif.c b/drivers/net/ethernet/hpe/ss1/cass_vf_notif.c
> new file mode 100644
> index 0000000000000..c46e198698736
> --- /dev/null
> +++ b/drivers/net/ethernet/hpe/ss1/cass_vf_notif.c
> @@ -0,0 +1,51 @@
[ ... ]
> +int dispatch_vf_notif(struct cass_dev *hw, const void *req, size_t req_len,
> + void *rsp, size_t *rsp_len)
> +{
> + const struct cass_vf_notif_common *common_req = req;
> + enum cass_vf_notif_ops op;
> + const struct cass_vf_notif_info *info;
> +
> + op = common_req->op;
> + if (op <= CASS_VF_NOTIF_OP_INVALID || op >= CASS_VF_NOTIF_OP_MAX)
> + return -EINVAL;
> +
> + info = &vf_notif_info[op];
> + if (!info->handler)
> + return -EOPNOTSUPP;
> +
> + if (req_len != info->req_size)
> + return -EINVAL;
[Severity: High]
What happens when a 0-byte or short message is received over vsock in
dispatch_vf_notif(), pf_probe_vfs(), cass_vf_init(), or pf_vf_msghandler()?
read_message_from_vsock() accepts any message with hdr.len <= *msg_len
(including hdr.len == 0, where it writes nothing to msg and returns
sizeof(hdr) > 0):
- In dispatch_vf_notif(), common_req->op is read before checking whether
req_len >= sizeof(*common_req).
- In pf_probe_vfs(), magic is set to CXI_SRIOV_MAGIC1 before sending and
msg_len == sizeof(magic) is not checked after read_message_from_vsock(),
so a 0-byte reply leaves magic == CXI_SRIOV_MAGIC1 and falsely matches
seq == 0.
- In cass_vf_init(), magic and token_resp are uninitialized on the stack,
and neither msg_len == sizeof(magic) after read_message_from_vsock() nor
resp_len == sizeof(token_resp) (or rc > 0) after cxi_send_msg_to_pf() is
verified before using them.
- In pf_vf_msghandler(), if the receive loop exits before invoking
hw->msg_relay(), reply_len is uninitialized on the stack when passed to
hw->msg_relay(hw->msg_relay_data, vf->vf_idx, NULL, 0, 0, 0, NULL,
&reply_len).
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261030-cxi-netdev-submission-v1-0-8c2c391cc283@hpe.com?part=19
next prev parent reply other threads:[~2026-10-31 21:02 UTC|newest]
Thread overview: 48+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-30 20:58 [PATCH 00/23] Subject: [PATCH net-next 00/23] net/hpe: add HPE Slingshot NIC drivers Doug Ledford
2026-10-30 20:58 ` [PATCH 01/23] net: hpe: ss1: add the Cassini headers Doug Ledford
2026-10-31 13:58 ` Andrew Lunn
2026-10-31 19:34 ` Doug Ledford
2026-10-30 20:58 ` [PATCH 02/23] net: hpe: sbl: add the Slingshot Base Link headers Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:58 ` [PATCH 03/23] net: hpe: sbl: add the core instance and configuration layer Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 04/23] net: hpe: sbl: add the SerDes layer Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 05/23] net: hpe: sbl: add the PML link layer Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 06/23] net: hpe: sbl: add link state management Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 07/23] net: hpe: sbl: add the SBM serial bus layer Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 08/23] net: hpe: sbl: add media, FEC and counter support Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 09/23] net: hpe: sl: add the Slingshot Link headers Doug Ledford
2026-10-30 20:59 ` [PATCH 10/23] net: hpe: sl: add the module entry points Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 11/23] net: hpe: sl: add the core link state machines Doug Ledford
2026-10-30 20:59 ` [PATCH 12/23] net: hpe: sl: add the control layer Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 13/23] net: hpe: sl: add media and cable handling Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 14/23] net: hpe: sl: add the sysfs interface Doug Ledford
2026-10-30 20:59 ` [PATCH 15/23] net: hpe: sl: add the NIC binding and logging helpers Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 16/23] net: hpe: ss1: add the Cassini core device layer Doug Ledford
2026-10-30 20:59 ` [PATCH 17/23] net: hpe: ss1: add the datapath and memory translation layers Doug Ledford
2026-10-30 20:59 ` [PATCH 18/23] net: hpe: ss1: add the link layer glue Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 19/23] net: hpe: ss1: add SR-IOV support Doug Ledford
2026-10-31 21:02 ` sashiko-bot [this message]
2026-10-30 20:59 ` [PATCH 20/23] net: hpe: ss1: add the CXI core interfaces Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 21/23] net: hpe: ss1: add resource groups, QoS and services Doug Ledford
2026-10-30 20:59 ` [PATCH 22/23] net: hpe: ss1: add the Ethernet personality Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 20:59 ` [PATCH 23/23] net: hpe: wire up the Slingshot NIC drivers Doug Ledford
2026-10-31 21:02 ` sashiko-bot
2026-10-30 22:11 ` [PATCH 00/23] Subject: [PATCH net-next 00/23] net/hpe: add HPE " Jakub Kicinski
2026-10-31 20:17 ` Andrew Lunn
2026-11-02 1:02 ` Doug Ledford
2026-11-02 1:35 ` Andrew Lunn
2026-11-02 3:45 ` Doug Ledford
2026-11-02 3:01 ` Guenter Roeck
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-192146@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=andrew+netdev@lunn.ch \
--cc=christian.koenig@amd.com \
--cc=davem@davemloft.net \
--cc=doug.ledford@hpe.com \
--cc=edumazet@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-kbuild@vger.kernel.org \
--cc=linux@roeck-us.net \
--cc=nathan@kernel.org \
--cc=nsc@kernel.org \
--cc=pabeni@redhat.com \
--cc=richardcochran@gmail.com \
--cc=sashiko-reviews@lists.linux.dev \
--cc=slingshot-os-team@groups.ext.hpe.com \
--cc=sumit.semwal@linaro.org \
/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