* [PATCH net v5 0/4] gve: Fixes for issues discovered via net selftests and Sashiko
@ 2026-11-09 18:20 Harshitha Ramamurthy
2026-11-09 18:20 ` [PATCH net v5 1/4] gve: Fix race between stats query and queue reconfig Harshitha Ramamurthy
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Harshitha Ramamurthy @ 2026-11-09 18:20 UTC (permalink / raw)
To: netdev
Cc: joshwash, hramamurthy, andrew+netdev, davem, edumazet, kuba,
pabeni, jordanrhee, willemb, przemyslaw.kitszel, nktgrg, maolson,
jacob.e.keller, pkaligineedi, yangchun, patricion, awogbemila,
csully, jonolson, lrizzo, stable, linux-kernel, Pin-yen Lin
From: Pin-yen Lin <treapking@google.com>
This patch series addresses several issues in the gve driver. All four of
these fixes were either uncovered by running the net selftests or
suggested by Sashiko in the previous version.
The series includes the following changes:
- Patch 1 introduces a spinlock (stats_lock) to robustly synchronize stats
queries (including ndo_get_stats64 and queue stats) against queue
teardown and restart, preventing UAF and out-of-bounds reads. This was
discovered by the drivers/net/stats.py selftest.
- Patch 2 fixes an issue where interface stats would go backwards when the
interface was brought down or its configuration was adjusted. This was
also discovered by the drivers/net/stats.py selftest.
- Patch 3 ensures the driver falls back to the default minimum ring size if
the corresponding device option values are exposed as 0. This prevents
userspace from configuring unexpectedly small ring sizes. This was
discovered by the drivers/net/ring_reconfig.py selftest.
- Patch 4 makes sure ethtool configuration modifications are done
synchronously before returning to the userspace. This was discovered by
the drivers/net/ping.py selftest.
Changes in v5:
- Link to v4: https://lore.kernel.org/netdev/20261029211634.2232784-1-hramamurthy@google.com/
- Patch 1: Also protect the single-queue restart writes with stats_lock
(moved from patch 2) (Sashiko)
- Patch 1: Add a Fixes tag for 2e5e0932dff5, and clarify the commit
message and the stats_lock comment
- Patch 2: Mention the ndo_get_stats64 counters in the commit message
and add a Fixes tag for f5cedc84a30d (Sashiko)
- Patch 4: Keep queueing the service task after the synchronous link
status update, so that resets requested during reset/suspend are
handled once the queues come back up (Sashiko)
- Patch 2: Document that stats_lock also protects the baseline stats
- Patch 3: Explain the single fallback for both minimums in the commit
message
- Rebased to the latest net tree
Changes in v4:
- Link to v3: https://lore.kernel.org/netdev/20261023204706.3112215-1-hramamurthy@google.com/
- Patch 1: Clarify the commit message and change the Fixes tag (Sashiko)
- Patch 2: Report the live XDP TX ring counters in the base stats and
take stats_lock around the priv->rx[idx] writes in
gve_rx_queue_start() (Sashiko)
- Patch 4: Take netdev_lock() around the link status update in
gve_service_task() (Sashiko)
Changes in v3:
- Link to v2: https://lore.kernel.org/netdev/20260425002450.163421-1-hramamurthy@google.com/
- Protect all stats queries and queue lifecycle updates with a spinlock
(Sashiko)
- Drop u64_stats_sync for the base stats and use the spinlock instead
- Save base stats in gve_queues_mem_remove() and gve_rx_queue_stop()
- Drop unused rx_dropped and tx_dropped accumulation in
gve_add_base_stats()
- Fix the Fixes tag of patch 4
- Refined the commit messages
- Rebased to the latest net tree
Changes in v2:
- Add a NULL pointer check in gve_get_ring_err_stats() (Sashiko)
- Use local variable to prevent inflates from u64_stats_fetch_retry()
(Sashiko)
- Add u64_stats_fetch/begin to protect base stats (Sashiko)
Debarghya Kundu (1):
gve: Fix backward stats when interface goes down or configuration is
adjusted
Pin-yen Lin (3):
gve: Fix race between stats query and queue reconfig
gve: Block invalid ring sizes when device options are 0
gve: Fix link flap after ethtool ops
drivers/net/ethernet/google/gve/gve.h | 12 +
drivers/net/ethernet/google/gve/gve_adminq.c | 4 +-
drivers/net/ethernet/google/gve/gve_main.c | 239 ++++++++++++++-----
3 files changed, 199 insertions(+), 56 deletions(-)
base-commit: 787268686c64be60023743b527645a907e38ccca
--
2.56.0.455.g6c5a503b38-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net v5 1/4] gve: Fix race between stats query and queue reconfig
2026-11-09 18:20 [PATCH net v5 0/4] gve: Fixes for issues discovered via net selftests and Sashiko Harshitha Ramamurthy
@ 2026-11-09 18:20 ` Harshitha Ramamurthy
2026-11-09 18:20 ` [PATCH net v5 2/4] gve: Fix backward stats when interface goes down or configuration is adjusted Harshitha Ramamurthy
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Harshitha Ramamurthy @ 2026-11-09 18:20 UTC (permalink / raw)
To: netdev
Cc: joshwash, hramamurthy, andrew+netdev, davem, edumazet, kuba,
pabeni, jordanrhee, willemb, przemyslaw.kitszel, nktgrg, maolson,
jacob.e.keller, pkaligineedi, yangchun, patricion, awogbemila,
csully, jonolson, lrizzo, stable, linux-kernel, Pin-yen Lin,
Debarghya Kundu
From: Pin-yen Lin <treapking@google.com>
Fix a concurrency bug where network statistics queries race with queue
reconfiguration or interface teardown, causing a Use-After-Free (UAF) or
NULL pointer dereference.
Currently, statistics queries access priv->tx and priv->rx without
locking. When an interface is brought down or reconfigured,
gve_queues_mem_remove() frees these arrays. A concurrent stats query
can dereference freed queue structures and crash the kernel.
This issue was discovered and actually triggered by the
drivers/net/stats.py selftest. When an interface was brought down,
querying per-queue statistics via Netlink caused a kernel NULL pointer
dereference in gve_get_rx_queue_stats():
BUG: kernel NULL pointer dereference, address: 00000000000000a0
RIP: 0010:gve_get_rx_queue_stats+0x17/0x50 [gve]
Call Trace:
<TASK>
netdev_stat_queue_sum+0x11c/0x250
netdev_nl_stats_by_netdev+0x21b/0x2d0
netdev_nl_qstats_get_dumpit+0xd2/0x220
genl_dumpit+0x32/0x90
netlink_dump+0x1d2/0x450
__netlink_dump_start+0x1db/0x2a0
genl_family_rcv_msg_dumpit+0x9b/0x100
...
The per-queue stats callbacks run under the netdev instance lock, so
this NULL dereference is not a race; it happens on any query while the
interface is down.
Additionally, concurrent stats queries during queue reconfiguration can
race with gve_queues_mem_remove(), causing a Use-After-Free.
This affects gve_get_stats(), which does not hold the instance lock.
gve_handle_report_stats() also runs without it; it is stopped before
queue teardown, but can still read priv->rx[idx] while the single-queue
restart path rewrites it.
Fix this by introducing stats_lock to serialize stats queries against
queue teardown. In gve_queues_mem_remove(), queue pointers are set to
NULL under stats_lock before freeing. Stats readers acquire the lock
and verify pointers are non-NULL before reading counters.
The per-queue stats callbacks already run under the instance lock, but
take stats_lock as well for consistency.
The single-queue restart path also rewrites priv->rx[idx] in place, so
gve_rx_queue_stop() and gve_rx_queue_start() do those writes under
stats_lock as well.
This change was tested using the DQO RDA queue format.
Cc: stable@vger.kernel.org
Fixes: f5cedc84a30d ("gve: Add transmit and receive support")
Fixes: 2e5e0932dff5 ("gve: add support for basic queue stats")
Co-developed-by: Debarghya Kundu <debarghyak@google.com>
Signed-off-by: Debarghya Kundu <debarghyak@google.com>
Signed-off-by: Pin-yen Lin <treapking@google.com>
Signed-off-by: Harshitha Ramamurthy <hramamurthy@google.com>
---
Changes in v5:
- Take stats_lock around the priv->rx[idx] writes in
gve_rx_queue_stop() and gve_rx_queue_start(), moved from patch 2
(Sashiko)
- Add a Fixes tag for 2e5e0932dff5, and clarify the commit message and
the stats_lock comment
Changes in v4:
- Clarify in the commit message that the qstats NULL dereference is not
a race, and which readers race with queue teardown (Sashiko)
- Change the Fixes tag to f5cedc84a30d (Sashiko)
Changes in v3:
- Retitled from "gve: Add NULL pointer checks for per-queue statistics"
- Protect all stats queries and queue lifecycle updates with spinlock
(sashiko)
- Refined the commit message
drivers/net/ethernet/google/gve/gve.h | 5 +++
drivers/net/ethernet/google/gve/gve_main.c | 43 ++++++++++++++++++++--
2 files changed, 44 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/google/gve/gve.h b/drivers/net/ethernet/google/gve/gve.h
index b7ce6e8632db..1911d05eb697 100644
--- a/drivers/net/ethernet/google/gve/gve.h
+++ b/drivers/net/ethernet/google/gve/gve.h
@@ -957,6 +957,11 @@ struct gve_priv {
struct workqueue_struct *gve_wq;
struct work_struct service_task;
struct work_struct stats_report_task;
+ /* Protects the priv->tx/priv->rx arrays, the in-place rewrite of
+ * priv->rx[idx] and the queue counts against stats readers that do
+ * not hold the netdev instance lock.
+ */
+ spinlock_t stats_lock;
unsigned long service_task_flags;
unsigned long state_flags;
diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c
index 2437cd8ba633..45868cc4dd25 100644
--- a/drivers/net/ethernet/google/gve/gve_main.c
+++ b/drivers/net/ethernet/google/gve/gve_main.c
@@ -69,6 +69,7 @@ static void gve_get_stats(struct net_device *dev, struct rtnl_link_stats64 *s)
int num_tx_queues;
int ring;
+ spin_lock_bh(&priv->stats_lock);
num_tx_queues = gve_num_tx_queues(priv);
if (priv->rx) {
for (ring = 0; ring < priv->rx_cfg.num_queues; ring++) {
@@ -96,6 +97,7 @@ static void gve_get_stats(struct net_device *dev, struct rtnl_link_stats64 *s)
s->tx_bytes += bytes;
}
}
+ spin_unlock_bh(&priv->stats_lock);
}
static int gve_alloc_flow_rule_caches(struct gve_priv *priv)
@@ -642,9 +644,13 @@ static void gve_queues_mem_remove(struct gve_priv *priv)
struct gve_rx_alloc_rings_cfg rx_alloc_cfg = {0};
gve_get_curr_alloc_cfgs(priv, &tx_alloc_cfg, &rx_alloc_cfg);
- gve_queues_mem_free(priv, &tx_alloc_cfg, &rx_alloc_cfg);
+
+ spin_lock_bh(&priv->stats_lock);
priv->tx = NULL;
priv->rx = NULL;
+ spin_unlock_bh(&priv->stats_lock);
+
+ gve_queues_mem_free(priv, &tx_alloc_cfg, &rx_alloc_cfg);
}
static void gve_free_control_plane_resources(struct gve_priv *priv)
@@ -1383,6 +1389,7 @@ static int gve_queues_start(struct gve_priv *priv,
int err;
/* Record new resources into priv */
+ spin_lock_bh(&priv->stats_lock);
priv->tx = tx_alloc_cfg->tx;
priv->rx = rx_alloc_cfg->rx;
@@ -1396,10 +1403,11 @@ static int gve_queues_start(struct gve_priv *priv,
priv->rx_pages_per_qpl = rx_alloc_cfg->pages_per_qpl;
priv->header_split_enabled = rx_alloc_cfg->enable_header_split;
priv->rx_cfg.packet_buffer_size = rx_alloc_cfg->packet_buffer_size;
+ gve_init_sync_stats(priv);
+ spin_unlock_bh(&priv->stats_lock);
gve_tx_start_rings(priv, gve_num_tx_queues(priv));
gve_rx_start_rings(priv, rx_alloc_cfg->qcfg_rx->num_queues);
- gve_init_sync_stats(priv);
err = netif_set_real_num_tx_queues(dev, priv->tx_cfg.num_queues);
if (err)
@@ -2266,6 +2274,7 @@ void gve_handle_report_stats(struct gve_priv *priv)
if (!gve_get_report_stats(priv))
return;
+ spin_lock_bh(&priv->stats_lock);
be64_add_cpu(&priv->stats_report->written_count, 1);
/* tx stats */
if (priv->tx) {
@@ -2330,6 +2339,7 @@ void gve_handle_report_stats(struct gve_priv *priv)
};
}
}
+ spin_unlock_bh(&priv->stats_lock);
}
/* Handle NIC status register changes, reset requests and report stats */
@@ -2679,8 +2689,10 @@ static int gve_rx_queue_stop(struct net_device *dev, void *per_q_mem, int idx)
gve_turnup_and_check_status(priv);
gve_per_q_mem = (struct gve_rx_ring *)per_q_mem;
+ spin_lock_bh(&priv->stats_lock);
*gve_per_q_mem = priv->rx[idx];
memset(&priv->rx[idx], 0, sizeof(priv->rx[idx]));
+ spin_unlock_bh(&priv->stats_lock);
return 0;
}
@@ -2734,7 +2746,9 @@ static int gve_rx_queue_start(struct net_device *dev,
return -EAGAIN;
gve_per_q_mem = (struct gve_rx_ring *)per_q_mem;
+ spin_lock_bh(&priv->stats_lock);
priv->rx[idx] = *gve_per_q_mem;
+ spin_unlock_bh(&priv->stats_lock);
/* Single-queue creation requires quiescence on all queues */
gve_turndown(priv);
@@ -2770,7 +2784,9 @@ static int gve_rx_queue_start(struct net_device *dev,
* which needs the rtnl lock, will not run till this func returns and
* its caller gives up the lock.
*/
+ spin_lock_bh(&priv->stats_lock);
memset(&priv->rx[idx], 0, sizeof(priv->rx[idx]));
+ spin_unlock_bh(&priv->stats_lock);
return err;
}
@@ -2786,9 +2802,15 @@ static void gve_get_rx_queue_stats(struct net_device *dev, int idx,
struct netdev_queue_stats_rx *rx_stats)
{
struct gve_priv *priv = netdev_priv(dev);
- struct gve_rx_ring *rx = &priv->rx[idx];
+ struct gve_rx_ring *rx;
unsigned int start;
+ spin_lock_bh(&priv->stats_lock);
+ if (!priv->rx)
+ goto unlock;
+
+ rx = &priv->rx[idx];
+
do {
start = u64_stats_fetch_begin(&rx->statss);
rx_stats->packets = rx->rpackets;
@@ -2796,20 +2818,32 @@ static void gve_get_rx_queue_stats(struct net_device *dev, int idx,
rx_stats->alloc_fail = rx->rx_skb_alloc_fail +
rx->rx_buf_alloc_fail;
} while (u64_stats_fetch_retry(&rx->statss, start));
+
+unlock:
+ spin_unlock_bh(&priv->stats_lock);
}
static void gve_get_tx_queue_stats(struct net_device *dev, int idx,
struct netdev_queue_stats_tx *tx_stats)
{
struct gve_priv *priv = netdev_priv(dev);
- struct gve_tx_ring *tx = &priv->tx[idx];
+ struct gve_tx_ring *tx;
unsigned int start;
+ spin_lock_bh(&priv->stats_lock);
+ if (!priv->tx)
+ goto unlock;
+
+ tx = &priv->tx[idx];
+
do {
start = u64_stats_fetch_begin(&tx->statss);
tx_stats->packets = tx->pkt_done;
tx_stats->bytes = tx->bytes_done;
} while (u64_stats_fetch_retry(&tx->statss, start));
+
+unlock:
+ spin_unlock_bh(&priv->stats_lock);
}
static void gve_get_base_stats(struct net_device *dev,
@@ -2935,6 +2969,7 @@ static int gve_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
}
INIT_WORK(&priv->service_task, gve_service_task);
INIT_WORK(&priv->stats_report_task, gve_stats_report_task);
+ spin_lock_init(&priv->stats_lock);
priv->tx_cfg.max_queues = max_tx_queues;
priv->rx_cfg.max_queues = max_rx_queues;
--
2.56.0.455.g6c5a503b38-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net v5 2/4] gve: Fix backward stats when interface goes down or configuration is adjusted
2026-11-09 18:20 [PATCH net v5 0/4] gve: Fixes for issues discovered via net selftests and Sashiko Harshitha Ramamurthy
2026-11-09 18:20 ` [PATCH net v5 1/4] gve: Fix race between stats query and queue reconfig Harshitha Ramamurthy
@ 2026-11-09 18:20 ` Harshitha Ramamurthy
2026-11-09 18:20 ` [PATCH net v5 3/4] gve: Block invalid ring sizes when device options are 0 Harshitha Ramamurthy
2026-11-09 18:20 ` [PATCH net v5 4/4] gve: Fix link flap after ethtool ops Harshitha Ramamurthy
3 siblings, 0 replies; 5+ messages in thread
From: Harshitha Ramamurthy @ 2026-11-09 18:20 UTC (permalink / raw)
To: netdev
Cc: joshwash, hramamurthy, andrew+netdev, davem, edumazet, kuba,
pabeni, jordanrhee, willemb, przemyslaw.kitszel, nktgrg, maolson,
jacob.e.keller, pkaligineedi, yangchun, patricion, awogbemila,
csully, jonolson, lrizzo, stable, linux-kernel, Debarghya Kundu,
Pin-yen Lin
From: Debarghya Kundu <debarghyak@google.com>
gve_get_base_stats() sets all the stats to 0, so the stats go backwards
when interface goes down or configuration is adjusted.
ndo_get_stats64 has the same problem, since gve_get_stats() only sums
the live rings, which are freed when the interface goes down.
This issue was discovered and actually triggered by the
drivers/net/stats.py selftest (check_down test case). Bringing down the
interface caused statistics counters to reset to 0, triggering selftest
assertion failures because netdevice stats must not go backwards.
Fix this by persisting baseline stats across queue teardown in
gve_queues_mem_remove() and single-queue stop in gve_rx_queue_stop().
The baseline is reported by both ndo_get_stats64 and
gve_get_base_stats().
Also report the live XDP TX ring counters in gve_get_base_stats(), as
those rings are not exposed as netdev queues.
This change was tested using the DQO RDA queue format.
Cc: stable@vger.kernel.org
Fixes: f5cedc84a30d ("gve: Add transmit and receive support")
Fixes: 2e5e0932dff5 ("gve: add support for basic queue stats")
Signed-off-by: Debarghya Kundu <debarghyak@google.com>
Co-developed-by: Pin-yen Lin <treapking@google.com>
Signed-off-by: Pin-yen Lin <treapking@google.com>
Signed-off-by: Harshitha Ramamurthy <hramamurthy@google.com>
---
Changes in v5:
- Move the stats_lock around the priv->rx[idx] writes in
gve_rx_queue_stop() and gve_rx_queue_start() to patch 1 (Sashiko)
- Mention the ndo_get_stats64 counters in the commit message and add a
Fixes tag for f5cedc84a30d (Sashiko)
- Document that stats_lock also protects the baseline stats
Changes in v4:
- Report the live XDP TX ring counters in gve_get_base_stats() (Sashiko)
- Take stats_lock around the priv->rx[idx] writes in
gve_rx_queue_start() (Sashiko)
Changes in v3:
- Drop u64_stats_sync for the base stats and use spinlock to protect
them instead
- Save base stats in gve_queues_mem_remove() instead of gve_close()
- Save base stats in gve_rx_queue_stop() for single-queue restarts
- Drop unused rx_dropped and tx_dropped accumulation in gve_add_base_stats()
- Refined the commit message
Changes in v2:
- Add a NULL pointer check in gve_get_ring_err_stats() (Sashiko)
- Use local variable to prevent inflates from u64_stats_fetch_retry()
(Sashiko)
- Add u64_stats_fetch/begin to protect base stats (Sashiko)
drivers/net/ethernet/google/gve/gve.h | 7 ++
drivers/net/ethernet/google/gve/gve_main.c | 125 +++++++++++++++++----
2 files changed, 110 insertions(+), 22 deletions(-)
diff --git a/drivers/net/ethernet/google/gve/gve.h b/drivers/net/ethernet/google/gve/gve.h
index 1911d05eb697..e509488a3d3e 100644
--- a/drivers/net/ethernet/google/gve/gve.h
+++ b/drivers/net/ethernet/google/gve/gve.h
@@ -871,6 +871,10 @@ struct gve_ctrl_ops {
int (*report_link_speed)(struct gve_priv *priv);
};
+struct gve_ring_err_stats {
+ u64 rx_alloc_fails;
+};
+
struct gve_priv {
struct net_device *dev;
struct gve_tx_ring *tx; /* array of tx_cfg.num_queues */
@@ -960,11 +964,14 @@ struct gve_priv {
/* Protects the priv->tx/priv->rx arrays, the in-place rewrite of
* priv->rx[idx] and the queue counts against stats readers that do
* not hold the netdev instance lock.
+ * Also protects base_net_stats and base_ring_err_stats.
*/
spinlock_t stats_lock;
unsigned long service_task_flags;
unsigned long state_flags;
+ struct gve_ring_err_stats base_ring_err_stats;
+ struct rtnl_link_stats64 base_net_stats;
struct gve_stats_report *stats_report;
u64 stats_report_len;
dma_addr_t stats_report_bus; /* dma address for the stats report */
diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c
index 45868cc4dd25..b5e2f39a903c 100644
--- a/drivers/net/ethernet/google/gve/gve_main.c
+++ b/drivers/net/ethernet/google/gve/gve_main.c
@@ -61,16 +61,44 @@ static netdev_tx_t gve_start_xmit(struct sk_buff *skb, struct net_device *dev)
return gve_tx_dqo(skb, dev);
}
-static void gve_get_stats(struct net_device *dev, struct rtnl_link_stats64 *s)
+static void gve_add_base_stats(struct gve_priv *priv,
+ struct rtnl_link_stats64 *s)
+{
+ struct rtnl_link_stats64 *base_stats = &priv->base_net_stats;
+
+ s->rx_packets += base_stats->rx_packets;
+ s->rx_bytes += base_stats->rx_bytes;
+ s->tx_packets += base_stats->tx_packets;
+ s->tx_bytes += base_stats->tx_bytes;
+}
+
+static void gve_add_tx_ring_range_stats(struct gve_priv *priv, int start_ring,
+ int end_ring, u64 *packets, u64 *bytes)
+{
+ int ring;
+
+ for (ring = start_ring; ring < end_ring; ring++) {
+ struct gve_tx_ring *tx = &priv->tx[ring];
+ unsigned int start;
+ u64 pkts, byts;
+
+ do {
+ start = u64_stats_fetch_begin(&tx->statss);
+ pkts = tx->pkt_done;
+ byts = tx->bytes_done;
+ } while (u64_stats_fetch_retry(&tx->statss, start));
+ *packets += pkts;
+ *bytes += byts;
+ }
+}
+
+static void gve_get_ring_stats(struct gve_priv *priv,
+ struct rtnl_link_stats64 *s)
{
- struct gve_priv *priv = netdev_priv(dev);
unsigned int start;
u64 packets, bytes;
- int num_tx_queues;
int ring;
- spin_lock_bh(&priv->stats_lock);
- num_tx_queues = gve_num_tx_queues(priv);
if (priv->rx) {
for (ring = 0; ring < priv->rx_cfg.num_queues; ring++) {
do {
@@ -84,19 +112,41 @@ static void gve_get_stats(struct net_device *dev, struct rtnl_link_stats64 *s)
s->rx_bytes += bytes;
}
}
- if (priv->tx) {
- for (ring = 0; ring < num_tx_queues; ring++) {
- do {
- start =
- u64_stats_fetch_begin(&priv->tx[ring].statss);
- packets = priv->tx[ring].pkt_done;
- bytes = priv->tx[ring].bytes_done;
- } while (u64_stats_fetch_retry(&priv->tx[ring].statss,
- start));
- s->tx_packets += packets;
- s->tx_bytes += bytes;
- }
+ if (priv->tx)
+ gve_add_tx_ring_range_stats(priv, 0, gve_num_tx_queues(priv),
+ &s->tx_packets, &s->tx_bytes);
+}
+
+static void gve_get_ring_err_stats(struct gve_priv *priv,
+ struct gve_ring_err_stats *err_stats)
+{
+ int ring;
+
+ if (!priv->rx)
+ return;
+
+ for (ring = 0; ring < priv->rx_cfg.num_queues; ring++) {
+ struct gve_rx_ring *rx = &priv->rx[ring];
+ unsigned int start;
+ u64 rx_alloc_fails;
+
+ do {
+ start = u64_stats_fetch_begin(&rx->statss);
+ rx_alloc_fails = rx->rx_skb_alloc_fail +
+ rx->rx_buf_alloc_fail;
+ } while (u64_stats_fetch_retry(&rx->statss, start));
+
+ err_stats->rx_alloc_fails += rx_alloc_fails;
}
+}
+
+static void gve_get_stats(struct net_device *dev, struct rtnl_link_stats64 *s)
+{
+ struct gve_priv *priv = netdev_priv(dev);
+
+ spin_lock_bh(&priv->stats_lock);
+ gve_get_ring_stats(priv, s);
+ gve_add_base_stats(priv, s);
spin_unlock_bh(&priv->stats_lock);
}
@@ -646,6 +696,9 @@ static void gve_queues_mem_remove(struct gve_priv *priv)
gve_get_curr_alloc_cfgs(priv, &tx_alloc_cfg, &rx_alloc_cfg);
spin_lock_bh(&priv->stats_lock);
+ gve_get_ring_stats(priv, &priv->base_net_stats);
+ gve_get_ring_err_stats(priv, &priv->base_ring_err_stats);
+
priv->tx = NULL;
priv->rx = NULL;
spin_unlock_bh(&priv->stats_lock);
@@ -2690,9 +2743,19 @@ static int gve_rx_queue_stop(struct net_device *dev, void *per_q_mem, int idx)
gve_per_q_mem = (struct gve_rx_ring *)per_q_mem;
spin_lock_bh(&priv->stats_lock);
+ priv->base_net_stats.rx_packets += priv->rx[idx].rpackets;
+ priv->base_net_stats.rx_bytes += priv->rx[idx].rbytes;
+ priv->base_ring_err_stats.rx_alloc_fails +=
+ priv->rx[idx].rx_skb_alloc_fail +
+ priv->rx[idx].rx_buf_alloc_fail;
*gve_per_q_mem = priv->rx[idx];
memset(&priv->rx[idx], 0, sizeof(priv->rx[idx]));
spin_unlock_bh(&priv->stats_lock);
+
+ gve_per_q_mem->rpackets = 0;
+ gve_per_q_mem->rbytes = 0;
+ gve_per_q_mem->rx_skb_alloc_fail = 0;
+ gve_per_q_mem->rx_buf_alloc_fail = 0;
return 0;
}
@@ -2850,12 +2913,30 @@ static void gve_get_base_stats(struct net_device *dev,
struct netdev_queue_stats_rx *rx,
struct netdev_queue_stats_tx *tx)
{
- rx->packets = 0;
- rx->bytes = 0;
- rx->alloc_fail = 0;
+ const struct gve_ring_err_stats *base_err_stats;
+ const struct rtnl_link_stats64 *base_stats;
+ struct gve_priv *priv;
- tx->packets = 0;
- tx->bytes = 0;
+ priv = netdev_priv(dev);
+ base_stats = &priv->base_net_stats;
+ base_err_stats = &priv->base_ring_err_stats;
+
+ spin_lock_bh(&priv->stats_lock);
+ rx->packets = base_stats->rx_packets;
+ rx->bytes = base_stats->rx_bytes;
+ rx->alloc_fail = base_err_stats->rx_alloc_fails;
+
+ tx->packets = base_stats->tx_packets;
+ tx->bytes = base_stats->tx_bytes;
+ /* XDP TX rings are not exposed as netdev queues, so report their live
+ * counters here to keep the device totals consistent with
+ * ndo_get_stats64.
+ */
+ if (priv->tx)
+ gve_add_tx_ring_range_stats(priv, priv->tx_cfg.num_queues,
+ gve_num_tx_queues(priv),
+ &tx->packets, &tx->bytes);
+ spin_unlock_bh(&priv->stats_lock);
}
static const struct netdev_stat_ops gve_stat_ops = {
--
2.56.0.455.g6c5a503b38-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net v5 3/4] gve: Block invalid ring sizes when device options are 0
2026-11-09 18:20 [PATCH net v5 0/4] gve: Fixes for issues discovered via net selftests and Sashiko Harshitha Ramamurthy
2026-11-09 18:20 ` [PATCH net v5 1/4] gve: Fix race between stats query and queue reconfig Harshitha Ramamurthy
2026-11-09 18:20 ` [PATCH net v5 2/4] gve: Fix backward stats when interface goes down or configuration is adjusted Harshitha Ramamurthy
@ 2026-11-09 18:20 ` Harshitha Ramamurthy
2026-11-09 18:20 ` [PATCH net v5 4/4] gve: Fix link flap after ethtool ops Harshitha Ramamurthy
3 siblings, 0 replies; 5+ messages in thread
From: Harshitha Ramamurthy @ 2026-11-09 18:20 UTC (permalink / raw)
To: netdev
Cc: joshwash, hramamurthy, andrew+netdev, davem, edumazet, kuba,
pabeni, jordanrhee, willemb, przemyslaw.kitszel, nktgrg, maolson,
jacob.e.keller, pkaligineedi, yangchun, patricion, awogbemila,
csully, jonolson, lrizzo, stable, linux-kernel, Pin-yen Lin
From: Pin-yen Lin <treapking@google.com>
On older device versions, minimum ring sizes in the device option are
reported as 0. The driver imports these 0 values, allowing userspace
(`ethtool -G`) to request invalidly small ring sizes.
This issue was discovered and actually triggered by the
drivers/net/ring_reconfig.py selftest (ringparam test case). Halving the
ring size down to 1 triggered a kernel NULL pointer dereference in
gve_tx_alloc_rings_dqo():
BUG: kernel NULL pointer dereference, address: 0000000000000002
RIP: 0010:gve_tx_alloc_rings_dqo+0x1c4/0x510 [gve]
Call Trace:
<TASK>
gve_queues_mem_alloc+0x29/0xa0 [gve]
gve_adjust_config+0x1b/0x140 [gve]
gve_set_ringparam+0x1f4/0x250 [gve]
ethnl_set_rings+0x260/0x440
...
Fix this by falling back to the driver's default minimum ring sizes
(GVE_DEFAULT_MIN_RX_RING_SIZE and GVE_DEFAULT_MIN_TX_RING_SIZE) whenever
the device option reports minimum ring sizes as 0.
The device reports either both minimum ring sizes or neither, so a
single fallback for both is sufficient.
This change was tested using the DQO RDA queue format.
Cc: stable@vger.kernel.org
Fixes: ed4fb326947d ("gve: add support to read ring size ranges from the device")
Signed-off-by: Pin-yen Lin <treapking@google.com>
Reviewed-by: Joshua Washington <joshwash@google.com>
Reviewed-by: Jordan Rhee <jordanrhee@google.com>
Signed-off-by: Harshitha Ramamurthy <hramamurthy@google.com>
---
Changes in v5:
- Explain the single fallback for both minimums in the commit message
Changes in v4:
- No changes
Changes in v3:
- Retitled and refined the commit message
drivers/net/ethernet/google/gve/gve_adminq.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/google/gve/gve_adminq.c b/drivers/net/ethernet/google/gve/gve_adminq.c
index 8b215fca9c1b..2c848f0c23dc 100644
--- a/drivers/net/ethernet/google/gve/gve_adminq.c
+++ b/drivers/net/ethernet/google/gve/gve_adminq.c
@@ -191,7 +191,9 @@ void gve_parse_device_option(struct gve_priv *priv,
*dev_op_modify_ring = (void *)(option + 1);
/* device has not provided min ring size */
- if (option_length == GVE_DEVICE_OPTION_NO_MIN_RING_SIZE)
+ if (option_length == GVE_DEVICE_OPTION_NO_MIN_RING_SIZE ||
+ be16_to_cpu((*dev_op_modify_ring)->min_rx_ring_size) == 0 ||
+ be16_to_cpu((*dev_op_modify_ring)->min_tx_ring_size) == 0)
priv->device_info.default_min_ring_size = true;
break;
case GVE_DEV_OPT_ID_FLOW_STEERING:
--
2.56.0.455.g6c5a503b38-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net v5 4/4] gve: Fix link flap after ethtool ops
2026-11-09 18:20 [PATCH net v5 0/4] gve: Fixes for issues discovered via net selftests and Sashiko Harshitha Ramamurthy
` (2 preceding siblings ...)
2026-11-09 18:20 ` [PATCH net v5 3/4] gve: Block invalid ring sizes when device options are 0 Harshitha Ramamurthy
@ 2026-11-09 18:20 ` Harshitha Ramamurthy
3 siblings, 0 replies; 5+ messages in thread
From: Harshitha Ramamurthy @ 2026-11-09 18:20 UTC (permalink / raw)
To: netdev
Cc: joshwash, hramamurthy, andrew+netdev, davem, edumazet, kuba,
pabeni, jordanrhee, willemb, przemyslaw.kitszel, nktgrg, maolson,
jacob.e.keller, pkaligineedi, yangchun, patricion, awogbemila,
csully, jonolson, lrizzo, stable, linux-kernel, Pin-yen Lin
From: Pin-yen Lin <treapking@google.com>
When modifying device features via ethtool, GVE causes a short link-down
after the ethtool command completes, which can break scripts that use
the network interface right after the ethtool command.
This issue was discovered and actually triggered by the
drivers/net/ping.py selftest. Modifying checksum offload features via
ethtool caused a transient carrier drop ("Lost carrier", DHCP lease lost),
causing the immediate ping invocation to fail.
Fix it by calling gve_turnup_and_check_status() in gve_queues_start()
instead of deferring the link status update to the service task, so the
carrier status is updated synchronously before returning to userspace.
Take netdev_lock() around the link status update in gve_service_task(),
so that it is serialized with the synchronous update. All other callers
of gve_handle_link_status() already hold the instance lock.
Still queue the service task after the synchronous update, so that a
reset requested while the service task was disabled during gve_reset()
or suspend is handled once the queues come back up.
This change was tested using the DQO RDA queue format.
Cc: stable@vger.kernel.org
Fixes: 3b7cc73628ff ("gve: Use link status register to report link status")
Signed-off-by: Pin-yen Lin <treapking@google.com>
Reviewed-by: Joshua Washington <joshwash@google.com>
Signed-off-by: Harshitha Ramamurthy <hramamurthy@google.com>
---
Changes in v5:
- Keep queueing the service task in gve_queues_start() so that a reset
requested while the service task was disabled is not delayed (Sashiko)
- Clarify which cases the service task kick covers in the comment and
the commit message
Changes in v4:
- Take netdev_lock() around the link status update in
gve_service_task() to serialize it with gve_queues_start() (Sashiko)
Changes in v3:
- Retitled from "gve: Make ethtool config changes synchronous"
- Fix the Fixes tag
- Refined the commit message
drivers/net/ethernet/google/gve/gve_main.c | 73 +++++++++++++---------
1 file changed, 43 insertions(+), 30 deletions(-)
diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c
index b5e2f39a903c..76ab304f1905 100644
--- a/drivers/net/ethernet/google/gve/gve_main.c
+++ b/drivers/net/ethernet/google/gve/gve_main.c
@@ -17,6 +17,7 @@
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
+#include <net/netdev_lock.h>
#include <net/netdev_queues.h>
#include <net/sch_generic.h>
#include <net/xdp_sock_drv.h>
@@ -1431,6 +1432,37 @@ static void gve_queues_stop(struct gve_priv *priv)
gve_rx_stop_rings(priv, priv->rx_cfg.num_queues);
}
+static void gve_handle_link_status(struct gve_priv *priv)
+{
+ bool link_up;
+
+ netdev_assert_locked(priv->dev);
+
+ if (!gve_get_napi_enabled(priv))
+ return;
+
+ link_up = READ_ONCE(priv->link_up);
+ if (link_up == netif_carrier_ok(priv->dev))
+ return;
+
+ if (link_up) {
+ netdev_info(priv->dev, "Device link is up.\n");
+ netif_carrier_on(priv->dev);
+ } else {
+ netdev_info(priv->dev, "Device link is down.\n");
+ netif_carrier_off(priv->dev);
+ }
+}
+
+static void gve_turnup_and_check_status(struct gve_priv *priv)
+{
+ const struct gve_ctrl_ops *ops = priv->ctrl_ops;
+
+ gve_turnup(priv);
+ ops->report_link_status(priv);
+ gve_handle_link_status(priv);
+}
+
/* The passed-in queue memory is stored into priv and the queues are made live.
* No memory is allocated. Passed-in memory is freed on errors.
*/
@@ -1494,7 +1526,11 @@ static int gve_queues_start(struct gve_priv *priv,
round_jiffies(jiffies +
msecs_to_jiffies(priv->stats_report_timer_period)));
- gve_turnup(priv);
+ gve_turnup_and_check_status(priv);
+ /* Also kick the service task. When the queues come back up after
+ * gve_reset() or resume, this handles a reset requested while the
+ * service task was disabled.
+ */
queue_work(priv->gve_wq, &priv->service_task);
priv->interface_up_cnt++;
return 0;
@@ -1570,26 +1606,6 @@ static int gve_close(struct net_device *dev)
return err;
}
-static void gve_handle_link_status(struct gve_priv *priv)
-{
- bool link_up;
-
- if (!gve_get_napi_enabled(priv))
- return;
-
- link_up = READ_ONCE(priv->link_up);
- if (link_up == netif_carrier_ok(priv->dev))
- return;
-
- if (link_up) {
- netdev_info(priv->dev, "Device link is up.\n");
- netif_carrier_on(priv->dev);
- } else {
- netdev_info(priv->dev, "Device link is down.\n");
- netif_carrier_off(priv->dev);
- }
-}
-
static int gve_configure_rings_xdp(struct gve_priv *priv,
u16 num_xdp_rings)
{
@@ -2064,15 +2080,6 @@ static void gve_turnup(struct gve_priv *priv)
gve_set_napi_enabled(priv);
}
-static void gve_turnup_and_check_status(struct gve_priv *priv)
-{
- const struct gve_ctrl_ops *ops = priv->ctrl_ops;
-
- gve_turnup(priv);
- ops->report_link_status(priv);
- gve_handle_link_status(priv);
-}
-
static struct gve_notify_block *gve_get_tx_notify_block(struct gve_priv *priv,
unsigned int txqueue)
{
@@ -2406,8 +2413,14 @@ static void gve_service_task(struct work_struct *work)
gve_handle_status(priv, status);
gve_handle_reset(priv);
+
+ /* Serialize with the synchronous link status updates, which all run
+ * under the netdev instance lock.
+ */
+ netdev_lock(priv->dev);
ops->report_link_status(priv);
gve_handle_link_status(priv);
+ netdev_unlock(priv->dev);
}
static void gve_set_netdev_xdp_features(struct gve_priv *priv)
--
2.56.0.455.g6c5a503b38-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-09 18:20 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-09 18:20 [PATCH net v5 0/4] gve: Fixes for issues discovered via net selftests and Sashiko Harshitha Ramamurthy
2026-11-09 18:20 ` [PATCH net v5 1/4] gve: Fix race between stats query and queue reconfig Harshitha Ramamurthy
2026-11-09 18:20 ` [PATCH net v5 2/4] gve: Fix backward stats when interface goes down or configuration is adjusted Harshitha Ramamurthy
2026-11-09 18:20 ` [PATCH net v5 3/4] gve: Block invalid ring sizes when device options are 0 Harshitha Ramamurthy
2026-11-09 18:20 ` [PATCH net v5 4/4] gve: Fix link flap after ethtool ops Harshitha Ramamurthy
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®