* [PATCH net v3 1/5] ice: extract __ice_vsi_free_stats()
2026-09-11 11:26 [PATCH net v3 0/5] ice: fix stats array overflow via proper realloc Przemek Kitszel
@ 2026-09-11 11:26 ` Przemek Kitszel
2026-09-11 11:26 ` [PATCH net v3 2/5] ice: extract ice_vsi_new_stat_arrays() Przemek Kitszel
` (4 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Przemek Kitszel @ 2026-09-11 11:26 UTC (permalink / raw)
To: netdev, Jakub Kicinski
Cc: Tony Nguyen, Aleksandr Loktionov, Michal Schmidt, intel-wired-lan,
edumazet, horms, pabeni, davem, Przemek Kitszel
Record the length of each ring stats array in struct ice_vsi_stats, so
that freeing the array entries no longer needs the owning VSI. Keep the
new fields in sync in both places that size the arrays.
With that, the body of ice_vsi_free_stats() becomes independent of the
VSI and can be split out as __ice_vsi_free_stats(). The @free_entries
parameter is always true here; a later commit adds a caller that frees
only the array container, after its entries have been handed over to a
freshly allocated stats structure.
No functional change intended. Just preparation to have easier time
reading subsequent commits.
Signed-off-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
---
drivers/net/ethernet/intel/ice/ice.h | 2 +
drivers/net/ethernet/intel/ice/ice_lib.c | 52 ++++++++++++++----------
2 files changed, 33 insertions(+), 21 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h
index f72bb1aa4067..cf1b0ba8dc29 100644
--- a/drivers/net/ethernet/intel/ice/ice.h
+++ b/drivers/net/ethernet/intel/ice/ice.h
@@ -328,6 +328,8 @@ enum ice_vsi_state {
struct ice_vsi_stats {
struct ice_ring_stats **tx_ring_stats; /* Tx ring stats array */
struct ice_ring_stats **rx_ring_stats; /* Rx ring stats array */
+ u16 tx_ring_stats_len; /* Length of the Tx ring stats array */
+ u16 rx_ring_stats_len; /* Length of the Rx ring stats array */
};
/* struct that defines a VSI, associated with a dev */
diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c
index 8cdc4fda89e9..cd78c5c69a30 100644
--- a/drivers/net/ethernet/intel/ice/ice_lib.c
+++ b/drivers/net/ethernet/intel/ice/ice_lib.c
@@ -330,42 +330,48 @@ static void ice_vsi_free_arrays(struct ice_vsi *vsi)
vsi->rxq_map = NULL;
}
+/* free single stats memory */
+static void __ice_vsi_free_stats(struct ice_vsi_stats *vsi_stat, bool free_entries)
+{
+ if (!vsi_stat)
+ return;
+
+ if (free_entries) {
+ for (int i = 0; i < vsi_stat->tx_ring_stats_len; i++) {
+ if (vsi_stat->tx_ring_stats[i]) {
+ kfree_rcu(vsi_stat->tx_ring_stats[i], rcu);
+ WRITE_ONCE(vsi_stat->tx_ring_stats[i], NULL);
+ }
+ }
+ for (int i = 0; i < vsi_stat->rx_ring_stats_len; i++) {
+ if (vsi_stat->rx_ring_stats[i]) {
+ kfree_rcu(vsi_stat->rx_ring_stats[i], rcu);
+ WRITE_ONCE(vsi_stat->rx_ring_stats[i], NULL);
+ }
+ }
+ }
+
+ kfree(vsi_stat->tx_ring_stats);
+ kfree(vsi_stat->rx_ring_stats);
+ kfree(vsi_stat);
+}
+
/**
* ice_vsi_free_stats - Free the ring statistics structures
* @vsi: VSI pointer
*/
static void ice_vsi_free_stats(struct ice_vsi *vsi)
{
struct ice_vsi_stats *vsi_stat;
struct ice_pf *pf = vsi->back;
- int i;
if (vsi->type == ICE_VSI_CHNL)
return;
if (!pf->vsi_stats)
return;
vsi_stat = pf->vsi_stats[vsi->idx];
- if (!vsi_stat)
- return;
-
- ice_for_each_alloc_txq(vsi, i) {
- if (vsi_stat->tx_ring_stats[i]) {
- kfree_rcu(vsi_stat->tx_ring_stats[i], rcu);
- WRITE_ONCE(vsi_stat->tx_ring_stats[i], NULL);
- }
- }
-
- ice_for_each_alloc_rxq(vsi, i) {
- if (vsi_stat->rx_ring_stats[i]) {
- kfree_rcu(vsi_stat->rx_ring_stats[i], rcu);
- WRITE_ONCE(vsi_stat->rx_ring_stats[i], NULL);
- }
- }
-
- kfree(vsi_stat->tx_ring_stats);
- kfree(vsi_stat->rx_ring_stats);
- kfree(vsi_stat);
+ __ice_vsi_free_stats(vsi_stat, true);
pf->vsi_stats[vsi->idx] = NULL;
}
@@ -539,11 +545,13 @@ static int ice_vsi_alloc_stat_arrays(struct ice_vsi *vsi)
kzalloc_objs(*vsi_stat->tx_ring_stats, vsi->alloc_txq);
if (!vsi_stat->tx_ring_stats)
goto err_alloc_tx;
+ vsi_stat->tx_ring_stats_len = vsi->alloc_txq;
vsi_stat->rx_ring_stats =
kzalloc_objs(*vsi_stat->rx_ring_stats, vsi->alloc_rxq);
if (!vsi_stat->rx_ring_stats)
goto err_alloc_rx;
+ vsi_stat->rx_ring_stats_len = vsi->alloc_rxq;
pf->vsi_stats[vsi->idx] = vsi_stat;
@@ -3048,6 +3056,7 @@ ice_vsi_realloc_stat_arrays(struct ice_vsi *vsi)
vsi_stat->tx_ring_stats = tx_ring_stats;
return -ENOMEM;
}
+ vsi_stat->tx_ring_stats_len = req_txq;
if (req_rxq < prev_rxq) {
for (i = req_rxq; i < prev_rxq; i++) {
@@ -3067,6 +3076,7 @@ ice_vsi_realloc_stat_arrays(struct ice_vsi *vsi)
vsi_stat->rx_ring_stats = rx_ring_stats;
return -ENOMEM;
}
+ vsi_stat->rx_ring_stats_len = req_rxq;
return 0;
}
--
2.51.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH net v3 2/5] ice: extract ice_vsi_new_stat_arrays()
2026-09-11 11:26 [PATCH net v3 0/5] ice: fix stats array overflow via proper realloc Przemek Kitszel
2026-09-11 11:26 ` [PATCH net v3 1/5] ice: extract __ice_vsi_free_stats() Przemek Kitszel
@ 2026-09-11 11:26 ` Przemek Kitszel
2026-09-11 11:26 ` [PATCH net v3 3/5] ice: extract ice_vsi_get_num_qs() Przemek Kitszel
` (3 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Przemek Kitszel @ 2026-09-11 11:26 UTC (permalink / raw)
To: netdev, Jakub Kicinski
Cc: Tony Nguyen, Aleksandr Loktionov, Michal Schmidt, intel-wired-lan,
edumazet, horms, pabeni, davem, Przemek Kitszel
Split the allocation of a struct ice_vsi_stats and its two ring stats
arrays out of ice_vsi_alloc_stat_arrays(), which is left with just the
lookup and the store into pf->vsi_stats[].
The sole caller keeps passing the very same queue counts as before,
vsi->alloc_txq and vsi->alloc_rxq. A later commit adds a second caller
in the rebuild path, which cannot use vsi->alloc_* because it has to
size the arrays before the VSI is reconfigured.
Note that the error path no longer clears pf->vsi_stats[vsi->idx]; the
early return above guarantees it is already NULL.
No functional change intended.
Signed-off-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
---
drivers/net/ethernet/intel/ice/ice_lib.c | 47 +++++++++++++-----------
1 file changed, 25 insertions(+), 22 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c
index cd78c5c69a30..9c2863fc696c 100644
--- a/drivers/net/ethernet/intel/ice/ice_lib.c
+++ b/drivers/net/ethernet/intel/ice/ice_lib.c
@@ -519,6 +519,30 @@ static irqreturn_t ice_msix_clean_rings(int __always_unused irq, void *data)
return IRQ_HANDLED;
}
+static struct ice_vsi_stats *ice_vsi_new_stat_arrays(int txq, int rxq)
+{
+ struct ice_ring_stats **tx_ring_stats;
+ struct ice_ring_stats **rx_ring_stats;
+ struct ice_vsi_stats *vsi_stat;
+
+ vsi_stat = kzalloc_obj(*vsi_stat);
+ tx_ring_stats = kzalloc_objs(*tx_ring_stats, txq);
+ rx_ring_stats = kzalloc_objs(*rx_ring_stats, rxq);
+ if (!vsi_stat || !tx_ring_stats || !rx_ring_stats) {
+ kfree(vsi_stat);
+ kfree(tx_ring_stats);
+ kfree(rx_ring_stats);
+ return NULL;
+ }
+
+ vsi_stat->tx_ring_stats = tx_ring_stats;
+ vsi_stat->rx_ring_stats = rx_ring_stats;
+ vsi_stat->tx_ring_stats_len = txq;
+ vsi_stat->rx_ring_stats_len = rxq;
+
+ return vsi_stat;
+}
+
/**
* ice_vsi_alloc_stat_arrays - Allocate statistics arrays
* @vsi: VSI pointer
@@ -537,33 +561,12 @@ static int ice_vsi_alloc_stat_arrays(struct ice_vsi *vsi)
/* realloc will happen in rebuild path */
return 0;
- vsi_stat = kzalloc_obj(*vsi_stat);
+ vsi_stat = ice_vsi_new_stat_arrays(vsi->alloc_txq, vsi->alloc_rxq);
if (!vsi_stat)
return -ENOMEM;
- vsi_stat->tx_ring_stats =
- kzalloc_objs(*vsi_stat->tx_ring_stats, vsi->alloc_txq);
- if (!vsi_stat->tx_ring_stats)
- goto err_alloc_tx;
- vsi_stat->tx_ring_stats_len = vsi->alloc_txq;
-
- vsi_stat->rx_ring_stats =
- kzalloc_objs(*vsi_stat->rx_ring_stats, vsi->alloc_rxq);
- if (!vsi_stat->rx_ring_stats)
- goto err_alloc_rx;
- vsi_stat->rx_ring_stats_len = vsi->alloc_rxq;
-
pf->vsi_stats[vsi->idx] = vsi_stat;
-
return 0;
-
-err_alloc_rx:
- kfree(vsi_stat->rx_ring_stats);
-err_alloc_tx:
- kfree(vsi_stat->tx_ring_stats);
- kfree(vsi_stat);
- pf->vsi_stats[vsi->idx] = NULL;
- return -ENOMEM;
}
/**
--
2.51.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH net v3 3/5] ice: extract ice_vsi_get_num_qs()
2026-09-11 11:26 [PATCH net v3 0/5] ice: fix stats array overflow via proper realloc Przemek Kitszel
2026-09-11 11:26 ` [PATCH net v3 1/5] ice: extract __ice_vsi_free_stats() Przemek Kitszel
2026-09-11 11:26 ` [PATCH net v3 2/5] ice: extract ice_vsi_new_stat_arrays() Przemek Kitszel
@ 2026-09-11 11:26 ` Przemek Kitszel
2026-09-11 11:26 ` [PATCH net v3 4/5] ice: rebuild ring stats arrays instead of reallocating them in place Przemek Kitszel
` (2 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Przemek Kitszel @ 2026-09-11 11:26 UTC (permalink / raw)
To: netdev, Jakub Kicinski
Cc: Tony Nguyen, Aleksandr Loktionov, Michal Schmidt, intel-wired-lan,
edumazet, horms, pabeni, davem, Przemek Kitszel
ice_vsi_set_num_qs() mixed two things: deciding how many queues a VSI
gets, and applying all the side effects of that decision. Split the
decision out into ice_vsi_get_num_qs(), returning both counts at once.
To make that possible, group alloc_txq and alloc_rxq in struct ice_vsi
with struct_group_tagged(), which gives the return type without adding
any new storage. Field order changes slightly, num_txq now follows
alloc_rxq, but nothing depends on it.
ice_vsi_get_num_qs() takes @held_txq and @held_rxq from the start, even
though the only caller so far passes zero for both. They let a caller
ask what the counts would be once the queues the VSI currently owns are
back in the PF pool. A later commit adds the caller that needs this: the
rebuild path has to size its ring stats arrays before ice_vsi_decfg()
releases the queues, yet the arrays must match what ice_vsi_set_num_qs()
computes afterwards.
No functional change intended.
Signed-off-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
---
drivers/net/ethernet/intel/ice/ice.h | 6 +-
drivers/net/ethernet/intel/ice/ice_lib.c | 86 ++++++++++++++----------
2 files changed, 55 insertions(+), 37 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h
index cf1b0ba8dc29..48f4312df4a4 100644
--- a/drivers/net/ethernet/intel/ice/ice.h
+++ b/drivers/net/ethernet/intel/ice/ice.h
@@ -403,9 +403,11 @@ struct ice_vsi {
u8 rx_mapping_mode; /* ICE_MAP_MODE_[CONTIG|SCATTER] */
u16 *txq_map; /* index in pf->avail_txqs */
u16 *rxq_map; /* index in pf->avail_rxqs */
- u16 alloc_txq; /* Allocated Tx queues */
+ struct_group_tagged(ice_vsi_alloc_queues_params, alloc_txq_rxq,
+ u16 alloc_txq; /* Allocated Tx queues */
+ u16 alloc_rxq; /* Allocated Rx queues */
+ );
u16 num_txq; /* Used Tx queues */
- u16 alloc_rxq; /* Allocated Rx queues */
u16 num_rxq; /* Used Rx queues */
u16 req_txq; /* User requested Tx queues */
u16 req_rxq; /* User requested Rx queues */
diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c
index 9c2863fc696c..998b9eb6e9fe 100644
--- a/drivers/net/ethernet/intel/ice/ice_lib.c
+++ b/drivers/net/ethernet/intel/ice/ice_lib.c
@@ -153,16 +153,56 @@ static void ice_vsi_set_num_desc(struct ice_vsi *vsi)
}
}
-static u16 ice_get_rxq_count(struct ice_pf *pf)
+static u16 ice_get_rxq_count(struct ice_pf *pf, u16 held)
{
- return min(ice_get_avail_rxq_count(pf),
- netif_get_num_default_rss_queues());
+ return min_t(u16, ice_get_avail_rxq_count(pf) + held,
+ netif_get_num_default_rss_queues());
}
-static u16 ice_get_txq_count(struct ice_pf *pf)
+static u16 ice_get_txq_count(struct ice_pf *pf, u16 held)
{
- return min(ice_get_avail_txq_count(pf),
- netif_get_num_default_rss_queues());
+ return min_t(u16, ice_get_avail_txq_count(pf) + held,
+ netif_get_num_default_rss_queues());
+}
+
+/* @held_txq, @held_rxq: queues the VSI still owns but is about to return to the
+ * PF pool, so that the result matches what it will be once they are back there.
+ */
+static struct ice_vsi_alloc_queues_params
+ice_vsi_get_num_qs(struct ice_vsi *vsi, u16 held_txq, u16 held_rxq)
+{
+ struct ice_vsi_alloc_queues_params qs = {};
+ struct ice_pf *pf = vsi->back;
+
+ switch (vsi->type) {
+ case ICE_VSI_PF:
+ qs.alloc_txq = vsi->req_txq ?: ice_get_txq_count(pf, held_txq);
+
+ /* only 1 Rx queue unless RSS is enabled */
+ if (!test_bit(ICE_FLAG_RSS_ENA, pf->flags))
+ qs.alloc_rxq = 1;
+ else
+ qs.alloc_rxq = vsi->req_rxq ?:
+ ice_get_rxq_count(pf, held_rxq);
+ break;
+ case ICE_VSI_SF:
+ case ICE_VSI_CTRL:
+ case ICE_VSI_LB:
+ qs.alloc_txq = 1;
+ qs.alloc_rxq = 1;
+ break;
+ case ICE_VSI_VF:
+ qs.alloc_txq = vsi->vf->num_req_qs ?: vsi->vf->num_vf_qs;
+ qs.alloc_rxq = qs.alloc_txq;
+ break;
+ case ICE_VSI_CHNL:
+ break;
+ default:
+ dev_warn(ice_pf_to_dev(pf), "Unknown VSI type %d\n", vsi->type);
+ return vsi->alloc_txq_rxq;
+ }
+
+ return qs;
}
/**
@@ -180,68 +220,44 @@ static void ice_vsi_set_num_qs(struct ice_vsi *vsi)
if (WARN_ON(vsi_type == ICE_VSI_VF && !vf))
return;
+ vsi->alloc_txq_rxq = ice_vsi_get_num_qs(vsi, 0, 0);
+
switch (vsi_type) {
case ICE_VSI_PF:
- if (vsi->req_txq) {
- vsi->alloc_txq = vsi->req_txq;
+ if (vsi->req_txq)
vsi->num_txq = vsi->req_txq;
- } else {
- vsi->alloc_txq = ice_get_txq_count(pf);
- }
+ if (vsi->req_rxq && test_bit(ICE_FLAG_RSS_ENA, pf->flags))
+ vsi->num_rxq = vsi->req_rxq;
pf->num_lan_tx = vsi->alloc_txq;
-
- /* only 1 Rx queue unless RSS is enabled */
- if (!test_bit(ICE_FLAG_RSS_ENA, pf->flags)) {
- vsi->alloc_rxq = 1;
- } else {
- if (vsi->req_rxq) {
- vsi->alloc_rxq = vsi->req_rxq;
- vsi->num_rxq = vsi->req_rxq;
- } else {
- vsi->alloc_rxq = ice_get_rxq_count(pf);
- }
- }
-
pf->num_lan_rx = vsi->alloc_rxq;
vsi->num_q_vectors = max(vsi->alloc_rxq, vsi->alloc_txq);
break;
case ICE_VSI_SF:
- vsi->alloc_txq = 1;
- vsi->alloc_rxq = 1;
vsi->num_q_vectors = 1;
vsi->irq_dyn_alloc = true;
break;
case ICE_VSI_VF:
if (vf->num_req_qs)
vf->num_vf_qs = vf->num_req_qs;
- vsi->alloc_txq = vf->num_vf_qs;
- vsi->alloc_rxq = vf->num_vf_qs;
/* pf->vfs.num_msix_per includes (VF miscellaneous vector +
* data queue interrupts). Since vsi->num_q_vectors is number
* of queues vectors, subtract 1 (ICE_NONQ_VECS_VF) from the
* original vector count
*/
vsi->num_q_vectors = vf->num_msix - ICE_NONQ_VECS_VF;
break;
case ICE_VSI_CTRL:
- vsi->alloc_txq = 1;
- vsi->alloc_rxq = 1;
vsi->num_q_vectors = 1;
break;
case ICE_VSI_CHNL:
- vsi->alloc_txq = 0;
- vsi->alloc_rxq = 0;
break;
case ICE_VSI_LB:
- vsi->alloc_txq = 1;
- vsi->alloc_rxq = 1;
/* A dummy q_vector, no actual IRQ. */
vsi->num_q_vectors = 1;
break;
default:
- dev_warn(ice_pf_to_dev(pf), "Unknown VSI type %d\n", vsi_type);
break;
}
--
2.51.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH net v3 4/5] ice: rebuild ring stats arrays instead of reallocating them in place
2026-09-11 11:26 [PATCH net v3 0/5] ice: fix stats array overflow via proper realloc Przemek Kitszel
` (2 preceding siblings ...)
2026-09-11 11:26 ` [PATCH net v3 3/5] ice: extract ice_vsi_get_num_qs() Przemek Kitszel
@ 2026-09-11 11:26 ` Przemek Kitszel
2026-09-11 11:26 ` [PATCH net v3 5/5] ice: fix stats array overflow when VF requests more queues Przemek Kitszel
2026-09-11 16:52 ` [PATCH net v3 0/5] ice: fix stats array overflow via proper realloc Jakub Kicinski
5 siblings, 0 replies; 8+ messages in thread
From: Przemek Kitszel @ 2026-09-11 11:26 UTC (permalink / raw)
To: netdev, Jakub Kicinski
Cc: Tony Nguyen, Aleksandr Loktionov, Michal Schmidt, intel-wired-lan,
edumazet, horms, pabeni, davem, Przemek Kitszel
ice_vsi_realloc_stat_arrays() resized the ring stats arrays in place
with krealloc_array(), sizing them from vsi->req_txq/req_rxq. That is
not what ice_vsi_set_num_qs() computes later in ice_vsi_cfg_def(), so
after a rebuild the arrays could end up shorter than vsi->alloc_txq /
vsi->alloc_rxq, and ice_vsi_alloc_ring_stats() then walked past their
end. Requesting fewer queues than the PF pool can hand out was enough
to trigger it.
Replace it with ice_vsi_resize_stat_arrays(), which allocates a fresh
struct ice_vsi_stats instead, sized with ice_vsi_get_num_qs() and the
queues ice_vsi_decfg() is about to return to the PF pool, which is
exactly what ice_vsi_set_num_qs() will compute once they are back
there. Copy the surviving entry pointers over and install the new
structure, all before ice_vsi_decfg() runs. The entries that did not
fit are freed together with the old container, via
__ice_vsi_free_stats() with @free_entries set to false.
Doing the allocation up front also means the failure path is a plain
unlock and return, with the VSI still fully configured, rather than a
half-torn-down VSI to unwind.
While at it, skip the stats handling for ICE_VSI_CHNL, which has no
entry in pf->vsi_stats[] to begin with.
Signed-off-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
---
this is an actual fix/improvement, that removes some of the tech debt,
but there is no Fixes tag, as this was not directly caused by an user
observable bug, rather as a pre-work / ai-review resolution for the next
commit
---
drivers/net/ethernet/intel/ice/ice_lib.c | 118 +++++++++++++----------
1 file changed, 69 insertions(+), 49 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c
index 998b9eb6e9fe..0cd54af40c37 100644
--- a/drivers/net/ethernet/intel/ice/ice_lib.c
+++ b/drivers/net/ethernet/intel/ice/ice_lib.c
@@ -2953,6 +2953,51 @@ ice_vsi_rebuild_get_coalesce(struct ice_vsi *vsi,
return vsi->num_q_vectors;
}
+static void ice_vsi_free_unused_stat_arrays(struct ice_vsi_stats *vsi_stat,
+ struct ice_vsi_stats *new_vsi_stat)
+{
+ int new_txq = new_vsi_stat->tx_ring_stats_len;
+ int new_rxq = new_vsi_stat->rx_ring_stats_len;
+ int prev_txq = vsi_stat->tx_ring_stats_len;
+ int prev_rxq = vsi_stat->rx_ring_stats_len;
+
+ for (int i = new_txq; i < prev_txq; i++) {
+ if (vsi_stat->tx_ring_stats[i]) {
+ kfree_rcu(vsi_stat->tx_ring_stats[i], rcu);
+ WRITE_ONCE(vsi_stat->tx_ring_stats[i], NULL);
+ }
+ }
+ for (int i = new_rxq; i < prev_rxq; i++) {
+ if (vsi_stat->rx_ring_stats[i]) {
+ kfree_rcu(vsi_stat->rx_ring_stats[i], rcu);
+ WRITE_ONCE(vsi_stat->rx_ring_stats[i], NULL);
+ }
+ }
+}
+
+static void ice_vsi_set_stat_arrays(struct ice_vsi *vsi,
+ struct ice_vsi_stats *new_vsi_stat)
+{
+ u16 new_txq, new_rxq, prev_txq, prev_rxq;
+ struct ice_vsi_stats *vsi_stat;
+ struct ice_pf *pf = vsi->back;
+
+ new_txq = new_vsi_stat->tx_ring_stats_len;
+ new_rxq = new_vsi_stat->rx_ring_stats_len;
+ vsi_stat = pf->vsi_stats[vsi->idx];
+ pf->vsi_stats[vsi->idx] = new_vsi_stat;
+ if (!vsi_stat)
+ return; /* don't copy if there is no source */
+
+ prev_txq = vsi_stat->tx_ring_stats_len;
+ prev_rxq = vsi_stat->rx_ring_stats_len;
+
+ memcpy(new_vsi_stat->tx_ring_stats, vsi_stat->tx_ring_stats,
+ sizeof(*vsi_stat->tx_ring_stats) * min(prev_txq, new_txq));
+ memcpy(new_vsi_stat->rx_ring_stats, vsi_stat->rx_ring_stats,
+ sizeof(*vsi_stat->rx_ring_stats) * min(prev_rxq, new_rxq));
+}
+
/**
* ice_vsi_rebuild_set_coalesce - set coalesce from earlier saved arrays
* @vsi: VSI connected with q_vectors
@@ -3039,63 +3084,38 @@ ice_vsi_rebuild_set_coalesce(struct ice_vsi *vsi,
}
/**
- * ice_vsi_realloc_stat_arrays - Frees unused stat structures or alloc new ones
- * @vsi: VSI pointer
+ * ice_vsi_resize_stat_arrays - resize ring stats arrays for new queue count
+ * @vsi: VSI to swap the ring stats arrays of
+ *
+ * Call while @vsi still owns its queues and before ice_vsi_decfg() returns them
+ * to the PF pool, so that the new size is what ice_vsi_set_num_qs() will compute
+ * afterwards. Surviving entries are carried over, the rest is freed.
+ *
+ * Returns 0 on success and negative value on failure
*/
-static int
-ice_vsi_realloc_stat_arrays(struct ice_vsi *vsi)
+static int ice_vsi_resize_stat_arrays(struct ice_vsi *vsi)
{
- u16 req_txq = vsi->req_txq ? vsi->req_txq : vsi->alloc_txq;
- u16 req_rxq = vsi->req_rxq ? vsi->req_rxq : vsi->alloc_rxq;
- struct ice_ring_stats **tx_ring_stats;
- struct ice_ring_stats **rx_ring_stats;
- struct ice_vsi_stats *vsi_stat;
+ struct ice_vsi_alloc_queues_params qs;
+ struct ice_vsi_stats *old_stat;
+ struct ice_vsi_stats *new_stat;
struct ice_pf *pf = vsi->back;
- u16 prev_txq = vsi->alloc_txq;
- u16 prev_rxq = vsi->alloc_rxq;
- int i;
- vsi_stat = pf->vsi_stats[vsi->idx];
+ if (vsi->type == ICE_VSI_CHNL)
+ return 0;
- if (req_txq < prev_txq) {
- for (i = req_txq; i < prev_txq; i++) {
- if (vsi_stat->tx_ring_stats[i]) {
- kfree_rcu(vsi_stat->tx_ring_stats[i], rcu);
- WRITE_ONCE(vsi_stat->tx_ring_stats[i], NULL);
- }
- }
- }
+ qs = ice_vsi_get_num_qs(vsi, vsi->alloc_txq + vsi->num_xdp_txq,
+ vsi->alloc_rxq);
- tx_ring_stats = vsi_stat->tx_ring_stats;
- vsi_stat->tx_ring_stats =
- krealloc_array(vsi_stat->tx_ring_stats, req_txq,
- sizeof(*vsi_stat->tx_ring_stats),
- GFP_KERNEL | __GFP_ZERO);
- if (!vsi_stat->tx_ring_stats) {
- vsi_stat->tx_ring_stats = tx_ring_stats;
+ new_stat = ice_vsi_new_stat_arrays(qs.alloc_txq, qs.alloc_rxq);
+ if (!new_stat)
return -ENOMEM;
- }
- vsi_stat->tx_ring_stats_len = req_txq;
- if (req_rxq < prev_rxq) {
- for (i = req_rxq; i < prev_rxq; i++) {
- if (vsi_stat->rx_ring_stats[i]) {
- kfree_rcu(vsi_stat->rx_ring_stats[i], rcu);
- WRITE_ONCE(vsi_stat->rx_ring_stats[i], NULL);
- }
- }
- }
-
- rx_ring_stats = vsi_stat->rx_ring_stats;
- vsi_stat->rx_ring_stats =
- krealloc_array(vsi_stat->rx_ring_stats, req_rxq,
- sizeof(*vsi_stat->rx_ring_stats),
- GFP_KERNEL | __GFP_ZERO);
- if (!vsi_stat->rx_ring_stats) {
- vsi_stat->rx_ring_stats = rx_ring_stats;
- return -ENOMEM;
+ old_stat = pf->vsi_stats[vsi->idx];
+ ice_vsi_set_stat_arrays(vsi, new_stat);
+ if (old_stat) {
+ ice_vsi_free_unused_stat_arrays(old_stat, new_stat);
+ __ice_vsi_free_stats(old_stat, false);
}
- vsi_stat->rx_ring_stats_len = req_rxq;
return 0;
}
@@ -3127,7 +3147,7 @@ int ice_vsi_rebuild(struct ice_vsi *vsi, u32 vsi_flags)
mutex_lock(&vsi->xdp_state_lock);
- ret = ice_vsi_realloc_stat_arrays(vsi);
+ ret = ice_vsi_resize_stat_arrays(vsi);
if (ret)
goto unlock;
--
2.51.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH net v3 5/5] ice: fix stats array overflow when VF requests more queues
2026-09-11 11:26 [PATCH net v3 0/5] ice: fix stats array overflow via proper realloc Przemek Kitszel
` (3 preceding siblings ...)
2026-09-11 11:26 ` [PATCH net v3 4/5] ice: rebuild ring stats arrays instead of reallocating them in place Przemek Kitszel
@ 2026-09-11 11:26 ` Przemek Kitszel
2026-09-11 16:52 ` [PATCH net v3 0/5] ice: fix stats array overflow via proper realloc Jakub Kicinski
5 siblings, 0 replies; 8+ messages in thread
From: Przemek Kitszel @ 2026-09-11 11:26 UTC (permalink / raw)
To: netdev, Jakub Kicinski
Cc: Tony Nguyen, Aleksandr Loktionov, Michal Schmidt, intel-wired-lan,
edumazet, horms, pabeni, davem, Przemek Kitszel
From: Michal Schmidt <mschmidt@redhat.com>
When a VF increases its queue count via VIRTCHNL_OP_REQUEST_QUEUES,
ice_vc_request_qs_msg() sets vf->num_req_qs and triggers a VF reset.
The reset calls ice_vf_reconfig_vsi(), which does ice_vsi_decfg()
followed by ice_vsi_cfg(). ice_vsi_decfg() does not free the per-ring
stats arrays. Inside ice_vsi_cfg_def(), ice_vsi_set_num_qs() updates
alloc_txq/alloc_rxq to the new larger value, but
ice_vsi_alloc_stat_arrays() returns early because the stats already
exist. ice_vsi_alloc_ring_stats() then iterates using the new larger
alloc_txq and writes beyond the bounds of the old, smaller
tx_ring_stats/rx_ring_stats pointer arrays, corrupting adjacent SLUB
metadata.
KASAN detects the bug:
==================================================================
BUG: KASAN: slab-out-of-bounds in ice_vsi_alloc_ring_stats+0x385/0x4a0 [ice]
Read of size 8 at addr ffff88810affea60 by task kworker/u131:7/221
CPU: 24 UID: 0 PID: 221 Comm: kworker/u131:7 Not tainted 7.1.0-rc1+ #1 PREEMPT(lazy)
...
Workqueue: ice ice_service_task [ice]
Call Trace:
<TASK>
...
kasan_report+0xd7/0x120
ice_vsi_alloc_ring_stats+0x385/0x4a0 [ice]
ice_vsi_cfg_def+0x12e2/0x2060 [ice]
ice_vsi_cfg+0xb5/0x3c0 [ice]
ice_reset_vf+0x858/0xf80 [ice]
ice_vc_request_qs_msg+0x1da/0x290 [ice]
ice_vc_process_vf_msg+0xb15/0x1430 [ice]
__ice_clean_ctrlq+0x70d/0x9d0 [ice]
ice_service_task+0x840/0xf20 [ice]
process_one_work+0x690/0xff0
worker_thread+0x4d9/0xd20
kthread+0x322/0x410
ret_from_fork+0x332/0x660
ret_from_fork_asm+0x1a/0x30
</TASK>
Allocated by task 2439:
kasan_save_stack+0x1c/0x40
kasan_save_track+0x10/0x30
__kasan_kmalloc+0x96/0xb0
__kmalloc_noprof+0x1d8/0x580
ice_vsi_cfg_def+0x115c/0x2060 [ice]
ice_vsi_cfg+0xb5/0x3c0 [ice]
ice_vsi_setup+0x180/0x320 [ice]
ice_start_vfs+0x1f3/0x590 [ice]
ice_ena_vfs+0x66d/0x798 [ice]
ice_sriov_configure.cold+0xe4/0x121 [ice]
sriov_numvfs_store+0x279/0x480
kernfs_fop_write_iter+0x331/0x4f0
vfs_write+0x4c4/0xe40
ksys_write+0x10c/0x240
do_syscall_64+0xd9/0x650
entry_SYSCALL_64_after_hwframe+0x76/0x7e
The buggy address belongs to the object at ffff88810affea40
which belongs to the cache kmalloc-32 of size 32
The buggy address is located 0 bytes to the right of
allocated 32-byte region [ffff88810affea40, ffff88810affea60)
...
==================================================================
ice_vsi_rebuild() handles this correctly by calling
ice_vsi_resize_stat_arrays() before reconfiguration, but
ice_vf_reconfig_vsi() was missing this call.
Fix by calling ice_vsi_resize_stat_arrays() in ice_vf_reconfig_vsi()
before ice_vsi_decfg(), mirroring the ice_vsi_rebuild() pattern. The
helper sizes the new arrays with ice_vsi_get_num_qs(), which for a VF
reads vf->num_req_qs directly, so there is nothing else to set up for
it.
See the linked RHEL Jira item for a reproducer.
Fixes: 2a2cb4c6c181 ("ice: replace ice_vf_recreate_vsi() with ice_vf_reconfig_vsi()")
Closes: https://redhat.atlassian.net/browse/RHEL-164321
Assisted-by: Claude:claude-opus-4-6 semcode
Signed-off-by: Michal Schmidt <mschmidt@redhat.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Co-developed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
Signed-off-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
---
v3: rebase on top of all the pre-work commits, now there should be neither
mismatch between the alloc-time and use-time sizes nor regression on
failed memory allocation
---
drivers/net/ethernet/intel/ice/ice_lib.h | 1 +
drivers/net/ethernet/intel/ice/ice_lib.c | 2 +-
drivers/net/ethernet/intel/ice/ice_vf_lib.c | 4 ++++
3 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_lib.h b/drivers/net/ethernet/intel/ice/ice_lib.h
index 49454d98dcfe..5236088c320b 100644
--- a/drivers/net/ethernet/intel/ice/ice_lib.h
+++ b/drivers/net/ethernet/intel/ice/ice_lib.h
@@ -67,6 +67,7 @@ void ice_vsi_decfg(struct ice_vsi *vsi);
void ice_dis_vsi(struct ice_vsi *vsi, bool locked);
int ice_vsi_rebuild(struct ice_vsi *vsi, u32 vsi_flags);
+int ice_vsi_resize_stat_arrays(struct ice_vsi *vsi);
int ice_vsi_cfg(struct ice_vsi *vsi);
struct ice_vsi *ice_vsi_alloc(struct ice_pf *pf);
void ice_vsi_free(struct ice_vsi *vsi);
diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c
index 0cd54af40c37..4949f29e538e 100644
--- a/drivers/net/ethernet/intel/ice/ice_lib.c
+++ b/drivers/net/ethernet/intel/ice/ice_lib.c
@@ -3093,7 +3093,7 @@ ice_vsi_rebuild_set_coalesce(struct ice_vsi *vsi,
*
* Returns 0 on success and negative value on failure
*/
-static int ice_vsi_resize_stat_arrays(struct ice_vsi *vsi)
+int ice_vsi_resize_stat_arrays(struct ice_vsi *vsi)
{
struct ice_vsi_alloc_queues_params qs;
struct ice_vsi_stats *old_stat;
diff --git a/drivers/net/ethernet/intel/ice/ice_vf_lib.c b/drivers/net/ethernet/intel/ice/ice_vf_lib.c
index 27e4acb1620f..fd70a93b9869 100644
--- a/drivers/net/ethernet/intel/ice/ice_vf_lib.c
+++ b/drivers/net/ethernet/intel/ice/ice_vf_lib.c
@@ -268,6 +268,10 @@ static int ice_vf_reconfig_vsi(struct ice_vf *vf)
vsi->flags = ICE_VSI_FLAG_NO_INIT;
+ err = ice_vsi_resize_stat_arrays(vsi);
+ if (err)
+ return err;
+
ice_vsi_decfg(vsi);
ice_fltr_remove_all(vsi);
--
2.51.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH net v3 0/5] ice: fix stats array overflow via proper realloc
2026-09-11 11:26 [PATCH net v3 0/5] ice: fix stats array overflow via proper realloc Przemek Kitszel
` (4 preceding siblings ...)
2026-09-11 11:26 ` [PATCH net v3 5/5] ice: fix stats array overflow when VF requests more queues Przemek Kitszel
@ 2026-09-11 16:52 ` Jakub Kicinski
2026-09-11 16:56 ` Jakub Kicinski
5 siblings, 1 reply; 8+ messages in thread
From: Jakub Kicinski @ 2026-09-11 16:52 UTC (permalink / raw)
To: Przemek Kitszel
Cc: netdev, Tony Nguyen, Aleksandr Loktionov, Michal Schmidt,
intel-wired-lan, edumazet, horms, pabeni, davem
On Fri, 11 Sep 2026 13:26:15 +0200 Przemek Kitszel wrote:
> Fix OOB access to the stats arrays.
I'm going to assume you mean this for iwl-net, there are already 15
patches from Tony in the queue for net.
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH net v3 0/5] ice: fix stats array overflow via proper realloc
2026-09-11 16:52 ` [PATCH net v3 0/5] ice: fix stats array overflow via proper realloc Jakub Kicinski
@ 2026-09-11 16:56 ` Jakub Kicinski
0 siblings, 0 replies; 8+ messages in thread
From: Jakub Kicinski @ 2026-09-11 16:56 UTC (permalink / raw)
To: Przemek Kitszel
Cc: netdev, Tony Nguyen, Aleksandr Loktionov, Michal Schmidt,
intel-wired-lan, edumazet, horms, pabeni, davem
On Fri, 11 Sep 2026 09:52:37 -0700 Jakub Kicinski wrote:
> On Fri, 11 Sep 2026 13:26:15 +0200 Przemek Kitszel wrote:
> > Fix OOB access to the stats arrays.
>
> I'm going to assume you mean this for iwl-net, there are already 15
> patches from Tony in the queue for net.
FWIW at a glance LGTM code-wise, there are some kdoc warnings tho
^ permalink raw reply [flat|nested] 8+ messages in thread