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, "Sandeep Bhoot" <sandeep.bhoot@hpe.com>
Subject: Re: [PATCH 22/23] net: hpe: ss1: add the Ethernet personality
Date: Sat, 31 Oct 2026 21:02:19 +0000 [thread overview]
Message-ID: <sashiko-outbox-192148@kernel.org> (raw)
In-Reply-To: <20261030-cxi-netdev-submission-v1-22-8c2c391cc283@hpe.com>
Thank you for your contribution! Sashiko AI review found 33 potential issue(s) to consider:
- [Critical] net: hpe: ss1: use-after-free and NULL dereference in eth_receive()
- [High] net: hpe: ss1: unlocked queue and CQ access in debugfs dump_dev()
- [High] net: hpe: ss1: lock order inversion between rtnl_lock and dev_list_mutex
- [High] net: hpe: ss1: skb leak and dangling napi.skb in eth_receive()
- [High] net: hpe: ss1: priority RX buffer leak on drop in eth_receive()
- [High] net: hpe: ss1: duplicate DMA post and page leak in append_rx_buffer()
- [High] net: hpe: ss1: uninitialized checksum_ctrl in cxi_eth_get_skb_checksum_settings()
- [High] net: hpe: ss1: TX queue stall in cxi_tx_eth_poll() on EQ credit check
- [High] net: hpe: ss1: DMA skb leak and missing BQL reset in free_tx_queue()
- [High] net: hpe: ss1: PTP packet truncation after cxi_eth_open() resets timestamp cfg
- [High] net: hpe: ss1: invalid RMU filter and RSS programming in cxi_eth_mac_addr()
- [High] net: hpe: ss1: NULL deref in cxi_set_channels() and div-by-zero in ringparam
- [High] net: hpe: ss1: invalid PTE and TXQ service limits in hw_setup()
- [High] net: hpe: ss1: page refcount leak and duplicate DMA post in append_rx_buffer()
- [High] net: hpe: ss1: service leak in hw_cleanup() and hw_setup() on async LNI free
- [High] net: hpe: ss1: shared IRQ notifier attached before NAPI and EQ pointer init
- [High] net: hpe: ss1: GFP_KERNEL sleep under addr_list_lock in cxi_eth_set_rx_mode()
- [High] net: hpe: ss1: divide-by-zero and ignored ring sizes in cxi_set_ringparam()
- [High] net: hpe: ss1: spinlock self-deadlock in cxi_rxtx_profile_list_destroy()
- [High] net: hpe: ss1: self-collision in unique_vni_space() on profile update
- [Medium] net: hpe: ss1: broken module_param types and queue bounds in init()
- [Medium] net: hpe: ss1: non-atomic updates to shared ndev->stats across queues
- [Medium] net: hpe: ss1: standard MAC and FEC stats duplicated in ethtool -S
- [Medium] net: hpe: ss1: eth_receive() ignores NETIF_F_RXCSUM feature flag
- [Medium] net: hpe: ss1: physical LAC leak and RX queue count clobber in hw_setup()
- [Medium] net: hpe: ss1: stale RSS indirection state in hw_setup() when RX channels is 1
- [Medium] net: hpe: ss1: missing decrement of buffer counters in free_rx_buffers()
- [Medium] net: hpe: ss1: standard FEC and MAC stats exposed in cxi_get_ethtool_stats()
- [Medium] net: hpe: ss1: premature link bounce and missing defaults in cxi_set_priv_flags()
- [Medium] net: hpe: ss1: missing VNI bounds check and u16 truncation in cxi_valid_vni()
- [Medium] net: hpe: ss1: TOCTOU race on VNI uniqueness in RX/TX profile list
- [Medium] net: hpe: ss1: EEPROM offset truncation in cxi_get_module_eeprom()
- [Low] net: hpe: ss1: missing kernel-parameters.txt entries for module params
--
commit 7e1b94faef8516c915b2c860b4b34cce75b248a1
Author: Doug Ledford <doug.ledford@hpe.com>
net: hpe: ss1: add the Ethernet personality
This patch adds the Ethernet netdev personality (cxi-eth) for the Cassini
NIC, including netdev and ethtool operations, hardware timestamp
configuration, RX/TX profile lists, and debugfs support.
> diff --git a/drivers/net/ethernet/hpe/ss1/cxi_eth.c b/drivers/net/ethernet/hpe/ss1/cxi_eth.c
> new file mode 100644
> index 0000000000000..f575847120f94
> --- /dev/null
> +++ b/drivers/net/ethernet/hpe/ss1/cxi_eth.c
> @@ -0,0 +1,443 @@
[ ... ]
> +unsigned int rss_indir_size = 64;
> +module_param(rss_indir_size, int, 0444);
> +MODULE_PARM_DESC(rss_indir_size, "Size of RSS indirection table");
> +
> +unsigned int max_rss_queues = 16;
> +module_param(max_rss_queues, int, 0444);
> +MODULE_PARM_DESC(max_rss_queues, "Maximum number of RSS queues (power of 2)");
> +
> +unsigned int max_tx_queues = 16;
> +module_param(max_tx_queues, int, 0444);
> +MODULE_PARM_DESC(max_tx_queues, "Maximum number of TX queues");
> +
> +unsigned int small_pkts_buf_size = CXI_ETH_1_MB;
> +module_param(small_pkts_buf_size, uint, 0444);
> +MODULE_PARM_DESC(small_pkts_buf_size, "Size of a small packets buffer (4 KiB to 1 GiB, 4 KiB aligned)");
> +
> +unsigned int small_pkts_buf_count = 4;
> +module_param(small_pkts_buf_count, uint, 0444);
> +MODULE_PARM_DESC(small_pkts_buf_count, "Number of small packets buffers (1 to 1024)");
> +
> +unsigned int large_pkts_buf_count = 1024;
> +module_param(large_pkts_buf_count, uint, 0444);
> +MODULE_PARM_DESC(large_pkts_buf_count, "Number of large packets buffers (1 to 10000)");
> +
> +unsigned int buffer_threshold = 256;
> +module_param(buffer_threshold, uint, 0444);
> +MODULE_PARM_DESC(buffer_threshold, "Ethernet packets with length greater than"
> + " or equal to this threshold are large packets (0 to 16383)");
> +
> +unsigned int lpe_cdt_thresh_id = 2;
> +module_param(lpe_cdt_thresh_id, uint, 0444);
> +MODULE_PARM_DESC(lpe_cdt_thresh_id, "LPE append credit id to use (0 to 3)");
> +
> +static unsigned int rx_repost_retry_ms = 20;
> +module_param(rx_repost_retry_ms, uint, 0444);
> +MODULE_PARM_DESC(rx_repost_retry_ms,
> + "Time to wait before retrying to repost an RX buffer, in ms");
[Severity: Low]
These module parameters in cxi_eth.c (including idc_dma_threshold) and
tx_eq_count in cxi_eth_ops.c can be set on the kernel command line at boot.
Should they be documented in Documentation/admin-guide/kernel-parameters.txt?
> +static struct dentry *cxieth_debug_dir;
> +unsigned long rx_repost_retry_jiffies;
[ ... ]
> +static const struct net_device_ops cxi_eth_netdev_ops = {
> + .ndo_open = cxi_eth_open,
> + .ndo_start_xmit = cxi_eth_start_xmit,
> + .ndo_stop = cxi_eth_close,
> + .ndo_set_mac_address = cxi_eth_mac_addr,
> + .ndo_set_rx_mode = cxi_eth_set_rx_mode,
> + .ndo_change_mtu = cxi_change_mtu,
> + .ndo_hwtstamp_get = cxi_eth_hwtstamp_get,
> + .ndo_hwtstamp_set = cxi_eth_hwtstamp_set,
> +};
[Severity: Medium]
Since cxi_eth_netdev_ops does not implement ndo_get_stats64, the driver
relies on the shared ndev->stats structure.
In cxi_eth_ops.c, eth_complete(), emit_idc(), emit_dma(), and eth_receive()
update ndev->stats.tx_packets, ndev->stats.tx_bytes, ndev->stats.tx_dropped,
ndev->stats.rx_packets, ndev->stats.rx_bytes, and ndev->stats.rx_dropped
using plain ++ and += operations from concurrent per-queue TX and RX paths.
Can these concurrent updates race and lose counter increments? Should the
driver either use DEV_STATS_INC() / DEV_STATS_ADD() or implement
ndo_get_stats64 with per-queue statistics?
> +/* Core is adding a new device */
> +static int add_device(struct cxi_dev *cxi_dev)
> +{
[ ... ]
> + ndev->hw_features = NETIF_F_HW_CSUM | NETIF_F_GSO | NETIF_F_SG |
> + NETIF_F_GRO | NETIF_F_RXCSUM | NETIF_F_HIGHDMA |
> + NETIF_F_RXHASH;
> +
> + ndev->features = ndev->hw_features;
> + ndev->vlan_features = ndev->hw_features;
> + ndev->priv_flags |= IFF_UNICAST_FLT | IFF_LIVE_ADDR_CHANGE;
> +
> + rc = netif_set_real_num_queues(ndev,
> + min_t(unsigned int, 8, max_tx_queues),
> + min_t(unsigned int, 8, max_rss_queues));
> + if (rc)
> + goto err_free_rxq;
> +
> + rc = register_netdev(ndev);
[Severity: High]
Can this lock ordering between dev_list_mutex in cxi_core.c and rtnl_lock
deadlock?
In cxi_core.c, cxi_register_client(), cxi_unregister_client(),
cxi_add_device(), and cxi_remove_device() hold dev_list_mutex while calling
add_device() -> register_netdev() and remove_device() -> unregister_netdev(),
which acquire rtnl_lock (establishing dev_list_mutex -> rtnl_lock).
However, when cxi_eth_mac_addr() is called under rtnl_lock, it calls:
cxi_eth_mac_addr()
-> cxi_set_nid_from_mac()
-> program_nid()
-> cxi_send_async_event()
which acquires dev_list_mutex in cxi_core.c while rtnl_lock is already held
(establishing rtnl_lock -> dev_list_mutex), and can also self-deadlock if a
NETDEV_REGISTER notifier changes the MAC address during register_netdev().
[ ... ]
> +static int __init init(void)
> +{
> + unsigned int new;
> + int ret;
> +
> + if (rss_indir_size && (!is_power_of_2(rss_indir_size) ||
> + rss_indir_size > CXI_ETH_MAX_INDIR_ENTRIES)) {
> + rss_indir_size = min_t(unsigned int,
> + roundup_pow_of_two(rss_indir_size),
> + CXI_ETH_MAX_INDIR_ENTRIES);
> + pr_info("Adjusting rss_indir_size to %d\n", rss_indir_size);
> + }
> +
> + if (max_rss_queues > CXI_ETH_MAX_RSS_QUEUES ||
> + !is_power_of_2(max_rss_queues)) {
> + max_rss_queues = min_t(unsigned int,
> + roundup_pow_of_two(max_rss_queues),
> + CXI_ETH_MAX_RSS_QUEUES);
> + pr_info("Adjusting max_rss_queues to %d\n", max_rss_queues);
> + }
> +
> + if (max_tx_queues > CXI_ETH_MAX_TX_QUEUES) {
> + max_tx_queues = CXI_ETH_MAX_TX_QUEUES;
> + pr_info("Adjusting max_tx_queues to %d\n", max_tx_queues);
> + }
[Severity: Medium]
There appear to be several issues with how these module parameters are
declared and validated in init():
1. rss_indir_size, max_rss_queues, and max_tx_queues are declared as unsigned
int, but registered with signed int in module_param(..., int, 0444). Does
this trigger a pointer-sign mismatch in param_check_int() and allow negative
values to wrap to large unsigned integers?
2. If max_rss_queues is 0, !is_power_of_2(0) is true and
roundup_pow_of_two(0) performs an undefined shift.
3. On 64-bit platforms, if rss_indir_size or max_rss_queues exceeds
0x80000000U (for example, when -1 is passed), roundup_pow_of_two() returns
0x100000000UL, which min_t(unsigned int, ...) truncates to 0 before comparing
against CXI_ETH_MAX_INDIR_ENTRIES or CXI_ETH_MAX_RSS_QUEUES.
4. max_tx_queues is not checked for 0, which will cause alloc_etherdev_mqs()
in add_device() to fail if max_tx_queues=0 is passed.
5. rx_repost_retry_ms and rx_repost_retry_jiffies are computed in init(), but
never referenced anywhere else in the driver. Should they be removed?
[ ... ]
> diff --git a/drivers/net/ethernet/hpe/ss1/cxi_eth_debugfs.c b/drivers/net/ethernet/hpe/ss1/cxi_eth_debugfs.c
> new file mode 100644
> index 0000000000000..310a5a6484b9c
> --- /dev/null
> +++ b/drivers/net/ethernet/hpe/ss1/cxi_eth_debugfs.c
> @@ -0,0 +1,206 @@
[ ... ]
> +static void dump_cq_info(struct seq_file *s, struct cxi_cq *cq)
> +{
> + seq_printf(s, " id: %u\n", cxi_cq_get_cqn(cq));
> + seq_printf(s, " ptrs: size32=%u, rp32=%llu, hw_rp32=%u, wp32=%llu, hw_wp32=%llu\n",
> + cq->size32, cq->rp32, cq->status->rd_ptr * 2,
> + cq->wp32, cq->hw_wp32);
> + seq_printf(s, " free CQ slots: %d\n", __cxi_cq_free_slots(cq));
> +}
[ ... ]
> +static int dump_dev(struct seq_file *s, void *unused)
> +{
> + struct cxi_eth *dev = s->private;
> + u64 min_ts = UINT_MAX;
> + u64 max_ts = 0;
> + int i;
> + int j;
> +
> + seq_printf(s, "rss_indir_size=%u\n", rss_indir_size);
> + seq_printf(s, "idc_dma_threshold=%u\n", idc_dma_threshold);
> + seq_printf(s, "small_pkts_buf_size=%u\n", small_pkts_buf_size);
> + seq_printf(s, "small_pkts_buf_count=%u\n", small_pkts_buf_count);
> + seq_printf(s, "large_pkts_buf_count=%u\n", large_pkts_buf_count);
> + seq_printf(s, "num rx_pending=%u\n", dev->ringparam.rx_pending);
> + seq_printf(s, "num rx_mini_pending=%u\n", dev->ringparam.rx_mini_pending);
> + seq_printf(s, "num tx_pending=%u\n", dev->ringparam.tx_pending);
> + seq_printf(s, "num_prio_buffers=%u\n", dev->num_prio_buffers);
> + seq_printf(s, "num_req_buffers=%u\n", dev->num_req_buffers);
> +
> + if (!dev->is_active)
> + return 0;
> +
> + seq_printf(s, "shared TGT REQ CQ=%u\n",
> + cxi_cq_get_cqn(dev->cq_tgt_req));
> +
> + for (i = 0; i < dev->res.rss_queues; i++) {
> + seq_printf(s, "RX queue %u\n", i);
> + dump_rx_queue(s, &dev->rxqs[i]);
> + }
> +
> + if (dev->res.rss_queues) {
> + seq_printf(s, "PTP RX queue %u\n", PTP_RX_Q);
> + dump_rx_queue(s, &dev->rxqs[PTP_RX_Q]);
> + }
> +
> + for (i = 0; i < dev->cur_txqs; i++) {
> + seq_printf(s, "TX queue %u\n", i);
> + dump_tx_queue(s, &dev->txqs[i], dev);
> + }
[Severity: High]
Can reading the debugfs file race with hw_cleanup() or cxi_set_channels() and
cause a NULL pointer dereference or use-after-free?
dump_dev() checks dev->is_active and then dereferences dev->cq_tgt_req,
dev->rxqs[i] (rx->eq, rx->pt, rx->cq_tgt_prio), and dev->txqs[i]
(txq->dev->ndev, txq->eq, txq->eth1_cq, txq->eth2_cq, txq->shared_cq) without
holding rtnl_lock() or any device lock.
Concurrently, hw_cleanup() can tear down and NULL out these pointers, or
cxi_set_rx_channels() / cxi_set_tx_channels() can shrink and free queues in
cxi_shrink_rx_channels() / cxi_shrink_tx_channels() before updating
dev->res.rss_queues and dev->cur_txqs.
In addition, dump_cq_info() calls __cxi_cq_free_slots(cq), which modifies
cq->rp32 without synchronization against the TX path.
> diff --git a/drivers/net/ethernet/hpe/ss1/cxi_eth_ops.c b/drivers/net/ethernet/hpe/ss1/cxi_eth_ops.c
> new file mode 100644
> index 0000000000000..d19a6a27aa23f
> --- /dev/null
> +++ b/drivers/net/ethernet/hpe/ss1/cxi_eth_ops.c
> @@ -0,0 +1,2367 @@
[ ... ]
> +static unsigned int eth_complete(struct cxi_eth *dev, struct tx_queue *txq,
> + const union c_event *event, int budget,
> + unsigned int *pkts, unsigned int *bytes)
> +{
> + struct net_device *ndev = dev->ndev;
> + struct sk_buff *skb;
> + enum c_return_code return_code;
> +
> + if (event->hdr.event_size == C_EVENT_SIZE_16_BYTE) {
> + return_code = event->init_short.return_code;
> + skb = (void *)event->init_short.user_ptr;
> + } else {
> + return_code = event->init_long.return_code;
> + skb = (void *)event->init_long.user_ptr;
> + }
> +
> + if (skb == NULL) {
> + /* Bad IDC commands will generate a SEND event on error. */
> + if (return_code == C_RC_OK)
> + netdev_warn_once(dev->ndev,
> + "Successful command with NULL skb\n");
> +
> + ndev->stats.tx_dropped++;
> +
> + return 0;
> + }
> +
> + if (return_code == C_RC_OK) {
> + ndev->stats.tx_packets++;
> + ndev->stats.tx_bytes += skb->len;
> + *pkts += 1;
> + *bytes += skb->len;
> + } else {
> + netdev_err_once(dev->ndev,
> + "DMA command failed to complete: %d\n",
> + return_code);
> + ndev->stats.tx_dropped++;
> + }
> +
> + if (skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS)
> + finalize_tx_tstamp(dev, skb);
> +
> + unmap_frags(&dev->cxi_dev->pdev->dev, skb);
> +
> + napi_consume_skb(skb, budget);
> +
> + return 1;
> +}
> +
> +static int cxi_tx_eth_poll(struct napi_struct *napi, int budget)
> +{
> + struct tx_queue *txq = container_of(napi, struct tx_queue, napi);
> + struct cxi_eth *dev = txq->dev;
> + struct netdev_queue *dev_queue =
> + netdev_get_tx_queue(dev->ndev, txq->id);
> + const union c_event *event;
> + int work_done = 0;
> + unsigned int event_count = 0;
> + unsigned int pkts = 0;
> + unsigned int bytes = 0;
> + unsigned int cdts = 0;
> +
> + txq->stats.polling++;
> +
> + while (work_done < budget && (event = cxi_eq_get_event(txq->eq))) {
> + switch (event->hdr.event_type) {
> + case C_EVENT_SEND:
> + cdts += eth_complete(dev, txq, event, budget, &pkts, &bytes);
> + work_done++;
> + break;
> + default:
> + netdev_warn(dev->ndev, "Unhandled event %u, rc %d\n",
> + event->hdr.event_type,
> + event->hdr.return_code);
> + print_hex_dump(KERN_INFO,
> + "cxi eth unexpected tx event: ",
> + DUMP_PREFIX_ADDRESS, 16, 1, event,
> + (8 << event->hdr.event_size), false);
> + }
> +
> + event_count++;
> + if (event_count == 16) {
> + cxi_eq_int_disable(txq->eq);
> + event_count = 0;
> +
> + atomic_add(cdts, &txq->dma_eq_cdt);
> + cdts = 0;
> + }
> + }
> +
> + BUG_ON(work_done > budget);
> +
> + /* Check to see if the TX queue needs to be re-enabled. */
> + if (netif_tx_queue_stopped(dev_queue)) {
> + if (__cxi_cq_free_slots(txq->shared_cq) >= 16 &&
> + __cxi_cq_free_slots(txq->eth1_cq) >= 16 &&
> + (!dev->eth2_active || __cxi_cq_free_slots(txq->eth2_cq) >= 16) &&
> + atomic_read(&txq->dma_eq_cdt) > 0) {
> + txq->stats.restarted++;
> + netif_tx_wake_queue(dev_queue);
> + }
> + }
> +
> + if (pkts)
> + netdev_tx_completed_queue(dev_queue, pkts, bytes);
> +
> + if (work_done < budget && napi_complete_done(napi, work_done)) {
> + /* Ack events and re-enable the EQ interrupt. */
> + cxi_eq_int_enable(txq->eq);
> + } else if (event_count) {
> + cxi_eq_int_disable(txq->eq);
> + }
> +
> + atomic_add(cdts, &txq->dma_eq_cdt);
> +
> + return work_done;
> +}
[Severity: High]
Can a TX queue stopped for lack of EQ credits (dma_eq_cdt <= 0) remain
permanently stalled here?
Inside the cxi_tx_eth_poll() loop, cdts is only added to txq->dma_eq_cdt when
event_count reaches 16. For the remaining events (< 16),
atomic_add(cdts, &txq->dma_eq_cdt) happens at the very end of the function,
after the netif_tx_queue_stopped() check has already evaluated
atomic_read(&txq->dma_eq_cdt) > 0 and after napi_complete_done() has
completed NAPI.
Also, in cxi_eth_start_xmit(), netif_tx_stop_queue() is called without a
barrier and without re-checking free CQ slots or dma_eq_cdt afterwards, which
can race with cxi_tx_eth_poll() completing the last in-flight packets.
[ ... ]
> +void free_tx_queue(struct tx_queue *tx)
> +{
> + netif_napi_del(&tx->napi);
> +
> + tx->untagged_cq = NULL;
> +
> + cxi_cq_free(tx->shared_cq);
> + tx->shared_cq = NULL;
> +
> + cxi_cq_free(tx->eth1_cq);
> + tx->eth1_cq = NULL;
> +
> + if (tx->eth2_cq) {
> + cxi_cq_free(tx->eth2_cq);
> + tx->eth2_cq = NULL;
> + }
> +
> + cxi_eq_free(tx->eq);
> + tx->eq = NULL;
[Severity: High]
Does free_tx_queue() leak in-flight DMA skbs and leave BQL state corrupted?
When emit_dma() transmits an skb, its fragments are DMA-mapped, bytes are
queued to BQL via __netdev_tx_sent_queue(), dev->tstamp_skb may be set, and
the skb pointer is passed to hardware in dma_eth.user_ptr.
However, free_tx_queue() frees the CQs and tx->eq without draining pending
C_EVENT_SEND events (to unmap and free in-flight skbs and clear
dev->tstamp_skb) and without calling netdev_tx_reset_queue() to reset BQL.
Additionally, in eth_complete(), when return_code != C_RC_OK, the skb is
freed without adding skb->len to *bytes for netdev_tx_completed_queue(),
leaving those bytes permanently queued in dql.
[ ... ]
> +int alloc_tx_queue(struct cxi_eth *dev, unsigned int id)
> +{
[ ... ]
> + tx->eq_attr.cpu_affinity = tx->cpu;
> + tx->eq = cxi_eq_alloc(dev->lni, tx->eq_md, &tx->eq_attr, tx_eq_cb, tx,
> + NULL, NULL);
> + if (IS_ERR(tx->eq)) {
> + rc = PTR_ERR(tx->eq);
> + goto err_unmap_eq_buf;
> + }
[ ... ]
> + tx->dev = dev;
> + tx->id = id;
> +
> + NETIF_NAPI_ADD_TX(dev->ndev, &tx->napi, cxi_tx_eth_poll);
[Severity: High]
Is it safe to call cxi_eq_alloc() before NETIF_NAPI_ADD_TX()?
In cass_eq.c, cxi_eq_alloc() attaches the EQ's event notifier onto a shared
MSI-X completion IRQ's atomic notifier chain (best_irq->nh). If another EQ
sharing that MSI-X vector triggers an interrupt before NETIF_NAPI_ADD_TX()
initializes tx->napi, cass_irq_int_handler() will invoke tx_eq_cb(tx) ->
napi_schedule(&tx->napi) while tx->napi is still zero-initialized (with
napi.poll == NULL).
The same ordering issue appears in alloc_rx_queue() (where cxi_eq_alloc() is
called before NETIF_NAPI_ADD()) and in hw_setup() for dev->err_eq (where
err_eq_cb() reads dev->err_eq before cxi_eq_alloc() has assigned its return
value to dev->err_eq, or while dev->err_eq still holds an ERR_PTR from a
previous failed setup).
[ ... ]
> +static int alloc_rx_buffers(struct rx_queue *rx, unsigned int count,
> + enum c_ptl_list ptl_list)
> +{
[ ... ]
> + if (ptl_list == C_PTL_LIST_PRIORITY)
> + dev->num_prio_buffers++;
> + else
> + dev->num_req_buffers++;
> + }
[Severity: Medium]
alloc_rx_buffers() increments dev->num_prio_buffers and dev->num_req_buffers,
but free_rx_buffers() and hw_cleanup() never decrement or reset them. Won't
these counters grow without bound across interface restarts or channel and
ring changes?
> +
> + return i;
> +
> +out_requeue:
> + list_add_tail(&buf->buf_list, &rx->rx_unallocated);
> +
> + return i;
> +}
> +
> +/* Re-post an RX buffer. The buffer is on the rx_ready list. */
> +static int append_rx_buffer(struct rx_queue *rx, struct rx_buffer *buf,
> + gfp_t gfp)
> +{
[ ... ]
> + cmd.start = buf->mapping;
> + cmd.lac = dev->phys_lac;
> + buf->cur_offset = 0;
> + buf->posted_count = 0;
> + posted_offset = 0;
> +
> + for (i = 0; i < rx->page_chunks; i++) {
> + cmd.start = buf->mapping + posted_offset;
> +
> + rc = cxi_cq_emit_target(rx->cq_tgt_prio, &cmd);
> + if (rc)
> + goto fail;
> +
> + get_page(buf->page);
> + buf->posted_count++;
> + posted_offset += dev->eth_info.max_segment_size;
> + rx->stats.append_prio++;
> + }
> + } else {
[ ... ]
> +fail:
> + if (rc) {
> + netdev_err_once(dev->ndev, "Append failed on list %d: %d\n",
> + buf->ptl_list, rc);
> + rx->stats.append_failed++;
> + } else {
> + list_del(&buf->buf_list);
> + list_add_tail(&buf->buf_list, &rx->rx_in_use);
> + }
> +
> + return rc;
> +}
[Severity: High]
What happens in append_rx_buffer() when rx->page_chunks > 1 (i.e. PAGE_SIZE >
dev->eth_info.max_segment_size) and cxi_cq_emit_target() fails on chunk i > 0?
Chunks 0 .. i - 1 have already been queued in rx->cq_tgt_prio and had
get_page(buf->page) called, but because rc != 0, buf is left on
rx->rx_ready.
On the next post_rx_buffers() call, append_rx_buffer() sees buf->page != NULL,
resets buf->cur_offset = 0 and buf->posted_count = 0 (leaking the i page
references from the partial post), and re-emits chunks 0 .. i - 1 to hardware
with the same DMA addresses and buffer_id. Should append_rx_buffer() verify
that rx->cq_tgt_prio has enough free slots for all rx->page_chunks before
emitting any commands?
[ ... ]
> +int hw_setup(struct cxi_eth *dev)
> +{
> + int rc;
> + int lac;
> + struct net_device *ndev = dev->ndev;
> + const struct cxi_rsrc_limits limits = {
> + .acs = {
> + .max = 1,
> + .res = 1,
> + },
> + .eqs = {
> + .max = 1 + max_rss_queues + (max_tx_queues * 2),
> + .res = 1,
> + },
> + .cts = {
> + .max = 0,
> + .res = 0,
> + },
> + .ptes = {
> + .max = 1 + max_rss_queues,
> + .res = 8,
> + },
> + .txqs = {
> + .max = max_tx_queues * 2,
> + .res = 1,
> + },
[Severity: High]
Are limits.ptes and limits.txqs sized properly here?
1. limits.ptes.max is 1 + max_rss_queues while limits.ptes.res is hardcoded
to 8. If max_rss_queues is 1, 2, or 4, limits.ptes.max < limits.ptes.res,
causing validate_descriptor() in cass_svc.c to fail cxi_svc_alloc() with
-EINVAL.
2. limits.txqs.max is max_tx_queues * 2, but when dev->eth2_active is true,
alloc_tx_queue() allocates 3 transmit CQs per TX queue (eth1_cq, shared_cq,
and eth2_cq). Won't cxi_cq_alloc() fail with -ENOSPC once 3 * num_tx_channels
exceeds 2 * max_tx_queues?
[ ... ]
> + lac = cxi_phys_lac_alloc(dev->lni);
> + if (lac < 0) {
> + rc = -EINVAL;
> + netdev_info(ndev, "failed to get a physical LAC: %d\n", rc);
> + goto err_free_ni;
> + }
> + dev->phys_lac = lac;
> +
> + /* Store PCPs for all the Eth Classes */
> + dev->eth1_pcp = cxi_get_tc_req_pcp(dev->cxi_dev, CXI_ETH_TC1);
> + if (dev->eth1_pcp < 0) {
> + rc = -EINVAL;
> + goto err_free_ni;
> + }
[Severity: Medium]
If cxi_get_tc_req_pcp() fails here, jumping to err_free_ni instead of
err_free_lac skips cxi_phys_lac_free(dev->lni, dev->phys_lac) and leaks
dev->phys_lac.
Also, in the error unwind at the bottom of hw_setup(), if
cxi_set_tx_channels() fails, err_set_rx_queues calls
cxi_set_rx_channels(dev, 1), which overwrites dev->ndev->real_num_rx_queues
to 1 instead of preserving the configured RX queue count.
[ ... ]
> + enable_rx_queue(&dev->rxqs[0]);
> + enable_rx_queue(&dev->rxqs[PTP_RX_Q]);
> + enable_tx_queue(&dev->txqs[0]);
> +
> + dev->res.rss_queues = 1;
> + rc = cxi_set_rx_channels(dev, dev->ndev->real_num_rx_queues);
[Severity: Medium]
Because hw_setup() sets dev->res.rss_queues = 1 right before calling
cxi_set_rx_channels(), if dev->ndev->real_num_rx_queues is 1,
cxi_set_rx_channels() sees num_rx_channels == dev->res.rss_queues and returns
early without setting dev->res.rss_indir_size = 0 or updating
dev->res.ptn_rss[0].
If the interface was previously open with multiple RX queues, closed,
configured to 1 RX queue via ethtool -L while down, and reopened, won't
dev->res.rss_indir_size and dev->res.ptn_rss[] retain stale values that
cxi_eth_set_rx_mode() -> cxi_eth_set_indir_table() then programs into the
hardware indirection table?
[ ... ]
> +void hw_cleanup(struct cxi_eth *dev)
> +{
[ ... ]
> + cxi_phys_lac_free(dev->lni, dev->phys_lac);
> + dev->phys_lac = 0;
> + cxi_lni_free(dev->lni);
> + dev->lni = NULL;
> + cxi_svc_destroy(dev->cxi_dev, dev->svc_id);
> +}
[Severity: High]
Does calling cxi_svc_destroy() immediately after cxi_lni_free() leak
dev->svc_id when LNI cleanup is deferred?
In cass_lni.c, if try_cleanup_lni() finds that any CQ or PTE has not finished
hardware drain, cxi_lni_free() queues lni_priv onto hw->lni_cleanups_list for
delayed work without decrementing the service rgroup refcount yet. The
immediate call to cxi_svc_destroy() in hw_cleanup() (and in hw_setup()'s
error path) then sees cxi_rgroup_refcount() > 1 and returns -EBUSY, leaving
the service and its resource reservations permanently leaked.
> +
> +static struct sk_buff *eth_rx_copy(struct rx_queue *rx,
> + struct rx_buffer *buf,
> + const struct c_event_target_enet *event)
> +{
[ ... ]
> +static struct sk_buff *eth_rx_frags(struct rx_queue *rx,
> + struct rx_buffer *buf,
> + const struct c_event_target_enet *event)
> +{
> + struct cxi_eth *eth_dev = rx->dev;
> + struct device *dev = ð_dev->cxi_dev->pdev->dev;
> + struct sk_buff *skb;
> + struct page *page;
> + int frag_len;
> +
> + if (unlikely(!buf->page || !buf->posted_count ||
> + buf->cur_offset >= PAGE_SIZE ||
> + event->length > PAGE_SIZE - buf->cur_offset ||
> + event->length > eth_dev->eth_info.max_segment_size))
> + return NULL;
> +
> + skb = napi_get_frags(&rx->napi);
> + if (unlikely(!skb))
> + return NULL;
> +
> + /* The SKB may already be full, due to the adapter
> + * being configured to fragment too much.
> + */
> + if (unlikely(skb_shinfo(skb)->nr_frags == MAX_SKB_FRAGS))
> + return NULL;
> +
> + page = buf->page;
> + frag_len = event->length;
> +
> + if (rx->frag_state == FRAG_NONE)
> + prefetch(page_address(page));
> +
> + skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
> + page, buf->cur_offset, frag_len,
> + eth_dev->eth_info.max_segment_size);
> + dma_sync_single_for_cpu(dev, buf->mapping + buf->cur_offset, frag_len,
> + DMA_FROM_DEVICE);
> +
> + buf->cur_offset += eth_dev->eth_info.max_segment_size;
> + buf->posted_count--;
> +
> + if (buf->posted_count == 0) {
> + dma_unmap_page(dev, dma_unmap_addr(buf, mapping), PAGE_SIZE,
> + DMA_FROM_DEVICE);
> + put_page(page);
> + buf->page = NULL;
> + }
> +
> + return skb;
> +}
[ ... ]
> +static bool eth_receive(struct rx_queue *rx,
> + const struct c_event_target_enet *event)
> +{
> + struct cxi_eth *dev = rx->dev;
> + struct net_device *ndev = dev->ndev;
> + struct sk_buff *skb = NULL;
> + struct rx_buffer *buf;
> + bool packet_complete = false;
> +
> + /* Find the target buffer */
> + if (event->ptlte_index != rx->pt->id) {
> + netdev_err(ndev, "Unknown portal target in RX event (%d instead of %d)\n",
> + event->ptlte_index, rx->pt->id);
> + return packet_complete;
> + }
> +
> + buf = &rx->rx_bufs[event->buffer_id];
> +
> + if (unlikely(event->return_code != C_RC_OK ||
> + (event->is_roce && !event->more_frags &&
> + !event->roce_icrc_ok) ||
> + event->parser_err ||
> + rx->frag_state == FRAG_DROP)) {
> + goto drop;
> + }
> +
> + if (event->ptl_list == C_PTL_LIST_PRIORITY)
> + skb = eth_rx_frags(rx, buf, event);
> + else
> + skb = eth_rx_copy(rx, buf, event);
> +
> + if (unlikely(!skb))
> + goto drop;
> +
> + if (dev->is_c2) {
> + if (event->seg_cnt == 0) {
> + /* It's a new packet */
> + rx->pkt_cnt = event->pkt_cnt;
> + rx->next_seg_cnt = 1;
> +
> + /* Free existing incomplete segments if any */
> + if (rx->frag_state != FRAG_NONE) {
> + ndev->stats.rx_dropped++;
> + rx->frag_state = FRAG_NONE;
> + napi_free_frags(&rx->napi);
> + }
> + } else if (event->pkt_cnt != rx->pkt_cnt ||
> + event->seg_cnt != rx->next_seg_cnt) {
> + goto drop;
> + } else {
> + rx->next_seg_cnt++;
> + }
> + }
[Severity: Critical]
In eth_receive(), does the dev->is_c2 segment check run too late and cause a
use-after-free and NULL pointer dereference?
When a previous multi-segment packet was incomplete (leaving rx->frag_state ==
FRAG_MORE and rx->napi.skb non-NULL) and the first segment of a new packet
arrives with event->seg_cnt == 0, eth_rx_frags() or eth_rx_copy() is called
before the dev->is_c2 check. Both functions see rx->frag_state != FRAG_NONE,
append the new packet's first segment onto rx->napi.skb via
napi_get_frags(&rx->napi), and return skb = rx->napi.skb.
Immediately afterwards, the dev->is_c2 block sees event->seg_cnt == 0 and
rx->frag_state != FRAG_NONE, and calls napi_free_frags(&rx->napi), freeing
the exact skb held in local variable skb and setting rx->napi.skb = NULL.
When event->more_frags == 0, execution continues below, writing to the freed
skb and calling napi_gro_frags(&rx->napi) (which dereferences rx->napi.skb ==
NULL) or napi_gro_receive(&rx->napi, skb) on the freed skb. Also, if
rx->frag_state == FRAG_DROP when a new packet starts with event->seg_cnt == 0,
the earlier check jumps to drop before the dev->is_c2 block can reset
rx->frag_state.
> +
> + if (event->more_frags == 0) {
> + __be16 csum;
> +
> + ndev->stats.rx_packets++;
> + ndev->stats.rx_bytes += skb->len;
> +
> + csum = (__force __be16)event->checksum;
> + skb->csum = (__force __wsum)be16_to_cpu(csum);
> + skb->ip_summed = CHECKSUM_COMPLETE;
[Severity: Medium]
Should setting skb->csum and skb->ip_summed = CHECKSUM_COMPLETE be gated on
(ndev->features & NETIF_F_RXCSUM), similar to how NETIF_F_RXHASH is checked
below?
> +
> + /* Cassini ERRATA-3258. This should test event->timestamp, but
> + * Cassini 1/1.1 doesn't set it.
> + */
> + if (dev->ptp_ts_enabled &&
> + event->ptlte_index == dev->res.ptn_ptp) {
> + struct c_ts ts;
> + int rc;
> +
> + rc = skb_copy_bits(skb, skb->len - sizeof(ts),
> + &ts, sizeof(ts));
> + if (rc)
> + goto drop;
> +
> + /* Cassini ERRATA-3276. Ignore the timestamp on Cassini 1
> + * if ns is close to 1s, as RX timestamp can
> + * be off by one second in that case. No issue
> + * on Cassini 2.
> + */
> + if (dev->is_c2 || ts.ns < 999999700)
> + skb_hwtstamps(skb)->hwtstamp =
> + ktime_set(ts.sec, ts.ns);
> +
> + pskb_trim(skb, skb->len - sizeof(ts));
> + }
[ ... ]
> + if (skb_is_nonlinear(skb)) {
> + napi_gro_frags(&rx->napi);
> +
> + rx->frag_state = FRAG_NONE;
> + } else {
> + napi_gro_receive(&rx->napi, skb);
> + }
> +
> + packet_complete = true;
> + } else {
> + rx->frag_state = FRAG_MORE;
> + }
> +
> +requeue:
> + if (event->ptl_list == C_PTL_LIST_PRIORITY) {
> + rx->stats.unlinked_prio++;
> +
> + if (buf->page == NULL) {
> + list_del(&buf->buf_list);
> + list_add_tail(&buf->buf_list, &rx->rx_ready);
> + }
> + } else if (event->auto_unlinked) {
> + rx->stats.unlinked_req++;
> + list_del(&buf->buf_list);
> + list_add_tail(&buf->buf_list, &rx->rx_ready);
> + }
> +
> + return packet_complete;
> +
> +drop:
> + /* Error on receive, NIC dropped this frag, or driver dropped a previous
> + * frag, or skb allocation failure
> + */
> + netdev_dbg(dev->ndev,
> + "received return_code=%d more=%d state=%d ptl_list=%d roce=%d\n",
> + event->return_code, event->more_frags, rx->frag_state,
> + event->ptl_list, event->is_roce);
> +
> + if (event->return_code != C_RC_OK)
> + rx->last_bad_frag_drop_rc = event->return_code;
> +
> + /* Not always a whole packet, so in theory a single packet
> + * could be counted several times, one for each segment.
> + */
> + ndev->stats.rx_dropped++;
> +
> + if (rx->frag_state != FRAG_DROP)
> + napi_free_frags(&rx->napi);
[Severity: High]
Does the drop path in eth_receive() leak standalone skbs and leave rx->napi.skb
dangling after pskb_trim()?
1. When eth_rx_copy() receives a single-fragment packet (rx->frag_state ==
FRAG_NONE && !event->more_frags), it allocates a standalone skb via
napi_alloc_skb(), which is not stored in rx->napi.skb. If eth_receive() then
jumps to drop (either from the dev->is_c2 check when event->seg_cnt != 0 or
when skb_copy_bits() fails on a short frame on the PTP queue), drop only calls
napi_free_frags(&rx->napi) and leaks skb.
2. If a multi-segment packet on the PTP queue has its first segment copied into
linear skb->data in eth_rx_copy() and <= sizeof(struct c_ts) bytes in
frags[0], pskb_trim(skb, skb->len - sizeof(ts)) reduces skb->data_len to 0 so
skb_is_nonlinear(skb) becomes false. Then eth_receive() calls
napi_gro_receive(&rx->napi, skb) instead of napi_gro_frags(&rx->napi),
consuming skb while leaving a dangling pointer in rx->napi.skb and leaving
rx->frag_state as FRAG_MORE.
[Severity: High]
Does dropping an event on C_PTL_LIST_PRIORITY leak the priority RX buffer?
Each C_EVENT_ETHERNET on C_PTL_LIST_PRIORITY consumes one use_once LE posted
by append_rx_buffer(). However, advancing buf->cur_offset, decrementing
buf->posted_count, and unmapping/putting buf->page when posted_count reaches 0
only happen at the end of eth_rx_frags().
If eth_receive() jumps to drop before calling eth_rx_frags() (e.g., on
return_code != C_RC_OK, !roce_icrc_ok, parser_err, or rx->frag_state ==
FRAG_DROP), or if eth_rx_frags() returns NULL early (when napi_get_frags()
fails or nr_frags == MAX_SKB_FRAGS), buf->posted_count is never decremented
and buf->cur_offset is not advanced. At requeue, buf->page remains non-NULL,
so buf is never moved back to rx->rx_ready and is leaked on rx->rx_in_use.
> +
> + if (event->more_frags)
> + rx->frag_state = FRAG_DROP;
> + else
> + rx->frag_state = FRAG_NONE;
> +
> + goto requeue;
> +}
[ ... ]
> +int cxi_eth_open(struct net_device *ndev)
> +{
> + struct cxi_eth *dev = netdev_priv(ndev);
> + struct hwtstamp_config ts_cfg = {
> + .flags = 0,
> + .tx_type = HWTSTAMP_TX_OFF,
> + .rx_filter = HWTSTAMP_FILTER_NONE,
> + };
[ ... ]
> + cxi_eth_cfg_timestamp(dev->cxi_dev, &ts_cfg);
> +
> + return hw_setup(dev);
> +}
[Severity: High]
If hardware timestamping was enabled via cxi_eth_hwtstamp_set() before
cxi_eth_open() (or prior to a close/open cycle), cxi_eth_open() disables
timestamping in hardware via cxi_eth_cfg_timestamp(dev->cxi_dev, &ts_cfg)
without updating dev->tstamp_config or clearing dev->ptp_ts_enabled.
When PTP L2 packets subsequently arrive on dev->res.ptn_ptp, hardware will not
append the 10-byte struct c_ts timestamp, but eth_receive() will still see
dev->ptp_ts_enabled == true and strip the last 10 bytes of the PTP packet
payload via pskb_trim().
Also, in tstamp_xmit(), should the driver check that
dev->tstamp_config.tx_type == HWTSTAMP_TX_ON before arming SKBTX_IN_PROGRESS
and reserving dev->tstamp_skb?
[ ... ]
> +static void cxi_eth_get_skb_checksum_settings(struct sk_buff *skb,
> + struct cxi_eth *dev,
> + enum c_checksum_ctrl *ctrl,
> + unsigned int *start,
> + unsigned int *offset)
> +{
> + __be16 proto;
> +
> + if (skb->ip_summed != CHECKSUM_PARTIAL)
> + goto no_crc;
> +
> + *start = skb_checksum_start_offset(skb) / 2;
> + *offset = skb->csum_offset / 2;
> +
> + if (eth_type_vlan(skb->protocol))
> + proto = vlan_get_protocol(skb);
> + else
> + proto = skb->protocol;
> +
> + if (proto == htons(ETH_P_IP)) {
> + struct iphdr *iph = ip_hdr(skb);
> +
> + if (iph->protocol == IPPROTO_UDP) {
> + if ((dev->priv_flags & CXI_ETH_PF_ROCE_OPT) &&
> + ntohs(udp_hdr(skb)->dest) == ROCE_V2_UDP_DPORT)
> + *ctrl = C_CHECKSUM_CTRL_ROCE;
> + else
> + *ctrl = C_CHECKSUM_CTRL_UDP;
> + } else if (iph->protocol == IPPROTO_TCP) {
> + *ctrl = C_CHECKSUM_CTRL_TCP;
> + }
> + } else if (proto == htons(ETH_P_IPV6)) {
> + struct ipv6hdr *iph = ipv6_hdr(skb);
> +
> + if (iph->nexthdr == IPPROTO_UDP) {
> + if ((dev->priv_flags & CXI_ETH_PF_ROCE_OPT) &&
> + ntohs(udp_hdr(skb)->dest) == ROCE_V2_UDP_DPORT)
> + *ctrl = C_CHECKSUM_CTRL_ROCE;
> + else
> + *ctrl = C_CHECKSUM_CTRL_UDP;
> + } else if (iph->nexthdr == IPPROTO_TCP) {
> + *ctrl = C_CHECKSUM_CTRL_TCP;
> + }
> + } else {
> + netdev_warn_once(dev->ndev,
> + "Checksum type not supported. Protocol=%u\n",
> + skb->protocol);
> + goto no_crc;
> + }
> +
> + return;
> +
> +no_crc:
> + *ctrl = C_CHECKSUM_CTRL_NONE;
> + *start = 0;
> + *offset = 0;
> +}
[Severity: High]
When skb->ip_summed == CHECKSUM_PARTIAL and the packet is IPv4 or IPv6, what
happens if iph->protocol or iph->nexthdr is neither IPPROTO_UDP nor
IPPROTO_TCP (for example, IPPROTO_ICMPV6 Neighbor Discovery or ping6, which
the stack sends with CHECKSUM_PARTIAL because ndev->hw_features advertises
NETIF_F_HW_CSUM)?
Neither the UDP nor TCP branch is taken, so cxi_eth_get_skb_checksum_settings()
returns without initializing *ctrl. Both emit_idc() and emit_dma() pass an
uninitialized stack variable checksum_ctrl and write it into
idc_eth.checksum_ctrl / dma_eth.checksum_ctrl, and the packet's L4 checksum is
never computed in software via skb_checksum_help().
[ ... ]
> +int cxi_eth_mac_addr(struct net_device *ndev, void *p)
> +{
> + struct cxi_eth *dev = netdev_priv(ndev);
> + int rc;
> +
> + rc = eth_mac_addr(ndev, p);
> + if (rc)
> + return rc;
> +
> + dev->mac_addr = ether_addr_to_u64(ndev->dev_addr);
> + ether_addr_copy(dev->cxi_dev->mac_addr, ndev->dev_addr);
> +
> + cxi_eth_set_rx_mode(ndev);
[Severity: High]
Should cxi_eth_mac_addr() only call cxi_eth_set_rx_mode(ndev) when
dev->is_active is true?
When the interface is down (!dev->is_active), RX queues and portals have not
been allocated (or have already been freed by hw_cleanup()), so calling
cxi_eth_set_rx_mode() programs live hardware RMU set_list filters with 0 or
freed portal IDs and leaks hw->set_list_table IDA entries if the device is
removed without being opened.
> +
> + /* We expect the NID to be configured before the MAC address is set.
> + * To be backwards compatible, we will attempt to set the NID from the MAC address.
> + * If the NID was not set, it will be derived from the MAC. Otherwise this will be ignored
> + */
> + cxi_set_nid_from_mac(dev->cxi_dev, ndev->dev_addr);
> +
> + return 0;
> +}
> +
> +/* Program MAC filters.
> + *
> + * Since the ethernet driver is the only thing using the MAC filter
> + * (set_list), we can reset the whole list when entering this
> + * function, and reprogram everything.
> + */
> +void cxi_eth_set_rx_mode(struct net_device *ndev)
> +{
> + struct cxi_eth *dev = netdev_priv(ndev);
> + struct netdev_hw_addr *ha;
> + int rc;
> +
> + /* Invalidate all existing filters. */
> + cxi_eth_set_list_invalidate_all(dev->cxi_dev, &dev->res);
> +
> + /* Cassini ERRATA-3258. Workaround timestamp bug. Program the PTP MAC
> + * address first so all the PTP packets will land on the PTP
> + * RX queue, even if promiscuous mode is enabled.
> + */
> + cxi_eth_add_mac(dev->cxi_dev, &dev->res, dev->ptp_mac_addr, true);
[Severity: High]
Can cxi_eth_set_rx_mode() sleep in atomic context when called via
.ndo_set_rx_mode?
The networking core calls .ndo_set_rx_mode while holding
netif_addr_lock_bh(ndev) (a bottom-half spinlock). cxi_eth_set_rx_mode() calls
cxi_eth_add_mac(), cxi_eth_set_promiscuous(), and cxi_eth_set_all_multi(), all
of which call cxi_eth_set_list() -> ida_alloc_max(..., GFP_KERNEL).
Conversely, when hw_setup() and cxi_eth_mac_addr() call
cxi_eth_set_rx_mode(ndev) directly, they do not hold netif_addr_lock_bh(ndev)
while iterating ndev->uc and ndev->mc and mutating dev->res.
> diff --git a/drivers/net/ethernet/hpe/ss1/cxi_ethtool.c b/drivers/net/ethernet/hpe/ss1/cxi_ethtool.c
> new file mode 100644
> index 0000000000000..67d8a0d23b903
> --- /dev/null
> +++ b/drivers/net/ethernet/hpe/ss1/cxi_ethtool.c
> @@ -0,0 +1,916 @@
[ ... ]
> +static void cxi_get_strings(struct net_device *ndev, u32 stringset, u8 *data)
> +{
> + char *p = data;
> + struct cxi_eth *dev = netdev_priv(ndev);
> + char *dst;
> +
> + switch (stringset) {
> + case ETH_SS_PRIV_FLAGS:
> + memcpy(p, priv_flags_str, PRIV_FLAGS_COUNT * ETH_GSTRING_LEN);
> + if (cassini_version(&dev->cxi_dev->prop, CASSINI_1)) {
> + dst = p + 7 * ETH_GSTRING_LEN;
> + memset(dst, 0, ETH_GSTRING_LEN);
> + strscpy(dst, "disable-pml-recovery", ETH_GSTRING_LEN);
> + }
> + break;
> + case ETH_SS_STATS:
> + memcpy(p, cxi_get_ethtool_stats_name, CXI_GLOBAL_STATS_LEN *
> + ETH_GSTRING_LEN);
> + break;
> +
> + }
> +}
[ ... ]
> +static int cxi_set_channels(struct net_device *ndev,
> + struct ethtool_channels *ch)
> +{
> + struct cxi_eth *dev = netdev_priv(ndev);
> + unsigned int old_tx_channels = dev->cur_txqs;
> + int rc;
> +
> + if (ch->combined_count || ch->other_count)
> + return -EINVAL;
> + if (!valid_rx_channel_count(ch->rx_count) || !ch->tx_count ||
> + ch->tx_count > ndev->num_tx_queues)
> + return -EINVAL;
> +
> + /* If interface is not running, update the real TX/RX queue count. The
> + * real TX/RX queue count is used to initialize TX/RX channels during
> + * hardware setup.
> + */
> + if (!netif_running(dev->ndev)) {
> + return netif_set_real_num_queues(ndev, ch->tx_count, ch->rx_count);
> + }
[Severity: High]
What happens in cxi_set_channels() if a prior cxi_change_mtu() or
cxi_set_ringparam() failed inside hw_setup(dev)?
In that state, netif_running(dev->ndev) is still true, but hw_cleanup(dev) has
set dev->is_active = false and freed dev->lni, dev->eth1_cp, and all queues.
Checking !netif_running(dev->ndev) instead of !dev->is_active allows
cxi_set_channels() to call cxi_set_tx_channels() and cxi_set_rx_channels(),
dereferencing NULL pointers.
[ ... ]
> +static int cxi_get_module_eeprom(struct net_device *ndev,
> + struct ethtool_eeprom *eeprom, u8 *data)
> +{
> + struct cxi_eth *dev = netdev_priv(ndev);
> + int ret;
> +
> + ret = cxi_get_qsfp_data(dev->cxi_dev, eeprom->offset, eeprom->len,
> + 0, data);
> +
> + if (ret > 0)
> + return 0;
> + else
> + return -EINVAL;
> +}
[Severity: Medium]
For SFF-8636 transceivers, cxi_get_module_info() sets modinfo->eeprom_len to
ETH_MODULE_SFF_8636_LEN (640).
In cxi_get_module_eeprom(), eeprom->offset (0..639) is passed directly to
cxi_get_qsfp_data() with page hardcoded to 0, where offset is stored into the
u8 field req_data->addr. Won't offsets >= 256 truncate to 8 bits and read page
0 instead of translating linear offsets 256..639 to pages 1..3?
[ ... ]
> +static int cxi_set_priv_flags(struct net_device *ndev, u32 flags)
> +{
[ ... ]
> + link_info.flags = flags;
> + cxi_link_mode_set(dev->cxi_dev, &link_info);
> +
> + if (changes & CXI_ETH_PF_FEC_MONITOR) {
[ ... ]
> +static u32 cxi_get_priv_flags(struct net_device *ndev)
> +{
> + struct cxi_eth *dev = netdev_priv(ndev);
> + struct cxi_link_info link_info;
> + u32 debug_flags;
> +
> + cxi_link_mode_get(dev->cxi_dev, &link_info);
> + cxi_link_flags_get(dev->cxi_dev, &debug_flags);
> +
> + return dev->priv_flags | link_info.flags | debug_flags;
> +}
[Severity: Medium]
In cxi_set_priv_flags(), cxi_link_mode_set() is called before the remaining
flag changes (such as CXI_ETH_PF_PML_REC, CXI_ETH_PF_ALD, and cable/media
flags) are applied to the device.
Also, on Cassini 2, cass_sl_config_init() enables pml-recovery and
auto-lane-degrade by default, but neither CXI_ETH_PF_PML_REC nor
CXI_ETH_PF_ALD is set in dev->priv_flags or returned by cxi_link_mode_get(),
so cxi_get_priv_flags() reports them as off.
> +
> +static void cxi_get_ethtool_stats(struct net_device *ndev,
> + struct ethtool_stats *stats, u64 *data)
> +{
> + struct cxi_eth *dev = netdev_priv(ndev);
> + struct cxi_dev *cdev = dev->cxi_dev;
> + int retval;
> + int i;
> +
> + retval = cxi_telem_get_selected(cdev, cxi_get_ethtool_stats_set, data,
> + CXI_GLOBAL_STATS_LEN);
> + if (retval != 0)
> + for (i = 0 ; i < CXI_GLOBAL_STATS_LEN ; ++i)
> + data[i] = 0;
> +}
[Severity: Medium]
cxi_get_ethtool_stats() and cxi_get_ethtool_stats_name expose standard IEEE
802.3 MAC and FEC codeword counters in ethtool -S, including rx_ok_ieee,
rx_bad_ieee, tx_ok_ieee, corrected_cw, and uncorrected_cw.
Per netdev statistics policy (Documentation/networking/statistics.rst),
shouldn't standard MAC and FEC statistics be reported through the dedicated
standard ethtool_ops callbacks (such as get_eth_mac_stats and get_fec_stats)
rather than duplicating them in ethtool -S?
[ ... ]
> +static int cxi_set_ringparam(struct net_device *ndev,
> + struct ethtool_ringparam *ring,
> + struct kernel_ethtool_ringparam *kernel_ring,
> + struct netlink_ext_ack *extack)
> +{
> + struct cxi_eth *dev = netdev_priv(ndev);
> + int rc = 0;
> +
> + if (ring->rx_jumbo_pending)
> + return -EINVAL;
> +
> + dev->ringparam.rx_pending = clamp(ring->rx_pending,
> + LARGE_PKTS_BUF_COUNT_MIN,
> + LARGE_PKTS_BUF_COUNT_MAX);
> +
> + dev->ringparam.rx_mini_pending = num_small_packets(
> + clamp(round_up(ring->rx_mini_pending, 4096) /
> + (small_pkts_buf_size / buffer_threshold),
> + SMALL_PKTS_BUF_COUNT_MIN, SMALL_PKTS_BUF_COUNT_MAX));
> +
> + dev->ringparam.tx_pending = clamp_t(unsigned int, ring->tx_pending,
> + 1, CXI_MAX_CQ_COUNT);
[Severity: High]
Can cxi_set_ringparam() divide by zero here when small_pkts_buf_size <
buffer_threshold?
In init(), small_pkts_buf_size can be as small as PAGE_SIZE (4096) while
buffer_threshold can be up to 16383. When small_pkts_buf_size <
buffer_threshold, (small_pkts_buf_size / buffer_threshold) evaluates to 0 in
both cxi_set_ringparam() and num_small_packets().
In addition, while cxi_set_ringparam() updates dev->ringparam.rx_mini_pending
and dev->ringparam.tx_pending, alloc_rx_queue() always uses
small_pkts_buf_count and alloc_tx_queue() always uses
min_t(unsigned int, CXI_MAX_CQ_COUNT, 2 * dev->ndev->tx_queue_len) (which also
sets force_dma_interval = 0 and CQ size < 16 if tx_queue_len < 8).
> diff --git a/drivers/net/ethernet/hpe/ss1/cxi_rxtx_profile.c b/drivers/net/ethernet/hpe/ss1/cxi_rxtx_profile.c
> new file mode 100644
> index 0000000000000..f5c978efd183d
> --- /dev/null
> +++ b/drivers/net/ethernet/hpe/ss1/cxi_rxtx_profile.c
> @@ -0,0 +1,418 @@
[ ... ]
> +/* Make sure the VNI space is unique */
> +bool unique_vni_space(struct cass_dev *hw,
> + struct cxi_rxtx_profile_list *list,
> + const struct cxi_rxtx_vni_attr *attr)
> +{
> + int rc;
> + struct cxi_rxtx_profile rxtx_prof = {
> + .vni_attr.match = attr->match,
> + .vni_attr.ignore = attr->ignore,
> + };
> +
> + cxi_rxtx_profile_list_lock(list);
> +
> + rc = cxi_rxtx_profile_list_iterate(list,
> + vni_overlap_test,
> + &rxtx_prof);
> +
> + cxi_rxtx_profile_list_unlock(list);
> +
> + return !rc;
> +}
[Severity: High]
When updating attributes on an existing RX or TX profile in the list (via
cxi_dev_set_rx_profile_attr() or cxi_dev_set_tx_profile_attr()), won't
unique_vni_space() compare the profile against its own entry in list?
Once a profile has a non-zero match value set, any subsequent update to
attr/ignore or attr/match will visit that same profile in vni_overlap_test(),
collide with itself, and fail with -EINVAL.
[Severity: Medium]
Also, because unique_vni_space() releases cxi_rxtx_profile_list_lock(list)
before cxi_dev_set_rx_profile_attr() / cxi_dev_set_tx_profile_attr() writes
the updated match and ignore values (and because cxi_rxtx_profile_list_insert()
passes GFP_KERNEL to __xa_alloc(), which can drop xa_lock to allocate a node
after the caller's overlap check), can two concurrent callers race and configure
overlapping VNI spaces?
> +
> +static int valid_vni_operator(struct cxi_rxtx_profile *rxtx_profile,
> + void *user_data)
> +{
> + int rc;
> + bool valid = false;
> + unsigned int ac_entry_id;
> + struct cxi_rxtx_vni_attr vni_attr;
> + struct cxi_rxtx_profile_state state;
> + struct valid_user_data *data = user_data;
> + struct cxi_rxtx_vni_attr user_vni = {
> + .match = data->vni,
> + };
[ ... ]
> +bool cxi_valid_vni(struct cxi_dev *dev, enum cxi_profile_type type,
> + unsigned int vni)
> +{
> + struct cxi_rxtx_profile_list *list;
> + bool valid;
> + struct cass_dev *hw = container_of(dev, struct cass_dev, cdev);
> + struct valid_user_data user_data = {
> + .vni = vni,
> + .ac_data = {
> + .uid = __kuid_val(current_euid()),
> + .gid = __kgid_val(current_egid()),
> + .netns = CURRENT_NETNS_ID(),
> + },
> + };
[Severity: Medium]
Should cxi_valid_vni() check is_vni_valid(vni) before searching the profile
list?
Currently, user_vni.match in valid_vni_operator() is a uint16_t, so an
out-of-range unsigned int vni (such as 0x10001 passed via cxi_cp_modify()) is
silently truncated to 16 bits and can match an enabled profile.
> diff --git a/drivers/net/ethernet/hpe/ss1/cxi_rxtx_profile_list.c b/drivers/net/ethernet/hpe/ss1/cxi_rxtx_profile_list.c
> new file mode 100644
> index 0000000000000..605f08abe53c2
> --- /dev/null
> +++ b/drivers/net/ethernet/hpe/ss1/cxi_rxtx_profile_list.c
> @@ -0,0 +1,249 @@
[ ... ]
> +void cxi_rxtx_profile_list_destroy(struct cxi_rxtx_profile_list *list,
> + void (*cleanup)(struct cxi_rxtx_profile *profile,
> + void *user_arg),
> + void *user_arg)
> +{
> + unsigned long index;
> + struct cxi_rxtx_profile *profile;
> +
> + index = list->limits->min;
> +
> + xa_lock(&list->xarray);
> +
> + xa_for_each(&list->xarray, index, profile) {
> + __xa_erase(&list->xarray, index);
> + if (cleanup)
> + cleanup(profile, user_arg);
> + }
> +
> + xa_unlock(&list->xarray);
> + xa_destroy(&list->xarray);
> +}
[Severity: High]
Does holding xa_lock(&list->xarray) across cleanup(profile, user_arg) cause a
spinlock self-deadlock (or refcount underflow and profile leak) when called
from cass_dev_rx_tx_profiles_fini()?
In cass_rx_tx_profile.c, cass_dev_rx_tx_profiles_fini() passes
rx_profile_destroy() as the cleanup callback:
cxi_rxtx_profile_list_destroy() [holds list->xarray.xa_lock]
-> rx_profile_destroy()
-> refcount_dec(&rxtx_profile->state.refcount)
-> cxi_rx_profile_dec_refcount()
-> cxi_rxtx_profile_list_remove()
-> xa_erase(&list->xarray, rxtx_profile_id)
When refcount_dec_and_test() in cxi_rx_profile_dec_refcount() reaches 0,
xa_erase() tries to acquire list->xarray.xa_lock while it is already held on
the same CPU. And if rxtx_profile->state.refcount was 1 on entry to
rx_profile_destroy(), the extra refcount_dec() drops it to 0 before
cxi_rx_profile_dec_refcount() calls refcount_dec_and_test(), underflowing the
refcount and returning -EBUSY without freeing the profile.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261030-cxi-netdev-submission-v1-0-8c2c391cc283@hpe.com?part=22
next prev parent reply other threads:[~2026-10-31 21:02 UTC|newest]
Thread overview: 52+ 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
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 [this message]
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 14:26 ` Andrew Lunn
2026-11-02 3:01 ` Guenter Roeck
2026-11-02 17:01 ` Doug Ledford
2026-11-02 17:39 ` Andrew Lunn
2026-11-02 18:12 ` Doug Ledford
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-192148@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=sandeep.bhoot@hpe.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