From: Harshitha Ramamurthy <hramamurthy@google.com>
To: netdev@vger.kernel.org
Cc: joshwash@google.com, hramamurthy@google.com,
andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@kernel.org,
kuba@kernel.org, pabeni@redhat.com, jordanrhee@google.com,
willemb@google.com, przemyslaw.kitszel@intel.com,
nktgrg@google.com, maolson@google.com, jacob.e.keller@intel.com,
pkaligineedi@google.com, yangchun@google.com,
patricion@google.com, awogbemila@google.com,
stable@vger.kernel.org, linux-kernel@vger.kernel.org,
Pin-yen Lin <treapking@google.com>,
Debarghya Kundu <debarghyak@google.com>
Subject: [PATCH net v3 1/4] gve: Fix race between stats query and queue reconfig
Date: Fri, 23 Oct 2026 20:47:03 +0000 [thread overview]
Message-ID: <20261023204706.3112215-2-hramamurthy@google.com> (raw)
In-Reply-To: <20261023204706.3112215-1-hramamurthy@google.com>
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
...
Additionally, concurrent stats queries during queue reconfiguration can
race with gve_queues_mem_remove(), causing a Use-After-Free.
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.
This change was tested using the DQO RDA queue format.
Cc: stable@vger.kernel.org
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 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 | 2 ++
drivers/net/ethernet/google/gve/gve_main.c | 37 +++++++++++++++++++---
2 files changed, 35 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/google/gve/gve.h b/drivers/net/ethernet/google/gve/gve.h
index b7ce6e8632db..104468c2c07f 100644
--- a/drivers/net/ethernet/google/gve/gve.h
+++ b/drivers/net/ethernet/google/gve/gve.h
@@ -957,6 +957,8 @@ struct gve_priv {
struct workqueue_struct *gve_wq;
struct work_struct service_task;
struct work_struct stats_report_task;
+ /* Synchronizes stats accesses and queue teardown */
+ 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..26fdd22644f4 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 */
@@ -2786,9 +2796,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 +2812,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 +2963,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.257.gf7497c7d27-goog
next prev parent reply other threads:[~2026-10-23 20:47 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-23 20:47 [PATCH net v3 0/4] gve: Fixes for issues discovered via net selftests and Sashiko Harshitha Ramamurthy
2026-10-23 20:47 ` Harshitha Ramamurthy [this message]
2026-10-23 20:47 ` [PATCH net v3 2/4] gve: Fix backward stats when interface goes down or configuration is adjusted Harshitha Ramamurthy
2026-10-23 20:47 ` [PATCH net v3 3/4] gve: Block invalid ring sizes when device options are 0 Harshitha Ramamurthy
2026-10-23 20:47 ` [PATCH net v3 4/4] gve: Fix link flap after ethtool ops Harshitha Ramamurthy
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=20261023204706.3112215-2-hramamurthy@google.com \
--to=hramamurthy@google.com \
--cc=andrew+netdev@lunn.ch \
--cc=awogbemila@google.com \
--cc=davem@davemloft.net \
--cc=debarghyak@google.com \
--cc=edumazet@kernel.org \
--cc=jacob.e.keller@intel.com \
--cc=jordanrhee@google.com \
--cc=joshwash@google.com \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=maolson@google.com \
--cc=netdev@vger.kernel.org \
--cc=nktgrg@google.com \
--cc=pabeni@redhat.com \
--cc=patricion@google.com \
--cc=pkaligineedi@google.com \
--cc=przemyslaw.kitszel@intel.com \
--cc=stable@vger.kernel.org \
--cc=treapking@google.com \
--cc=willemb@google.com \
--cc=yangchun@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is 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®