Skip to content

gve: fix AF_XDP pool teardown ordering - #96

Open
m4r1k wants to merge 1 commit into
GoogleCloudPlatform:releasefrom
m4r1k:fix/xsk-pool-disable-ordering
Open

gve: fix AF_XDP pool teardown ordering#96
m4r1k wants to merge 1 commit into
GoogleCloudPlatform:releasefrom
m4r1k:fix/xsk-pool-disable-ordering

Conversation

@m4r1k

@m4r1k m4r1k commented Aug 10, 2026

Copy link
Copy Markdown

Summary

Fix two ordering problems while disabling an AF_XDP zero-copy pool:

  • keep the UMEM DMA mapping valid until the datapath that can still use it has been quiesced;
  • return after DQO ring reconfiguration, which has already stopped the old queues, unregistered their XDP/XSK state, and started replacement queues;
  • use the NAPI helpers for callers that already hold the netdev operation lock in the remaining GQI path.

Root cause

gve_xsk_pool_disable() currently calls xsk_pool_dma_unmap() before stopping DQO RX. An in-flight gve_rx_xsk_dqo() can consequently reach xsk_buff_dma_sync_for_cpu() after the pool's DMA device has been cleared. In the observed fault, this produced a NULL-device access at 0x31c:

BUG: kernel NULL pointer dereference, address: 000000000000031c
RIP: gve_rx_xsk_dqo+0x55/0x440 [gve]
RDI: 0000000000000000
CR2: 000000000000031c

On DQO, gve_configure_rings_xdp() already tears down the old queues and creates replacements without the pool after its bitmap bit is cleared. Falling through afterward operates on the replacement rings. On kernels where XSK pool setup is serialized by the netdev operation lock, the subsequent plain napi_disable() can also self-deadlock:

Workqueue: events xp_release_deferred
napi_disable+0x1d/0x50
gve_xsk_pool_disable+0xed/0x1d0 [gve]
gve_xdp+0x14a/0x1c0 [gve]
xp_disable_drv_zc+0x89/0xe0
xp_clear_dev+0x59/0xf0
xp_release_deferred+0x20/0x90

Reproducer

The failure was reproduced on two Compute Engine c4n-highcpu-16 VMs using the DQO RDA queue format and Ubuntu 26.04 kernel 7.0.0-1008-gcp. The in-tree driver reports version 1.0.0; the affected teardown sequence is also present in official GVE v1.4.11.

Configure the gVNIC for native XDP, repeatedly bind AF_XDP zero-copy sockets to all queues, send traffic, then close the sockets and detach XDP:

sudo ethtool -K ens3 rx-gro-hw off
sudo ethtool -G ens3 rx-buf-len 2048 tcp-data-split off
sudo ethtool -L ens3 rx 8 tx 8

The baseline produced either the gve_rx_xsk_dqo() NULL dereference above or tasks stuck in gve_xsk_pool_disable() during socket release.

Validation

The patched v1.4.11 module was built against the exact 7.0.0-1008-gcp headers and loaded on both VMs. Module SHA-256:

881afb1de651d165f6b15dbf9a0098ee6f4e15bda02b3cf2311dd28cb186d9db

Results:

  • two paired 1-queue native zero-copy attach/traffic/detach cycles, including unlimited-rate traffic;
  • 16 paired 8-queue cycles, including a reboot from the persistently installed module;
  • three warm-ups and nine measured 60-second runs across 64-byte, 1000-byte, and IMIX traffic;
  • no recovery resets, NULL dereferences, hung tasks, NAPI warnings, or GVE resets in either serial console after the patch;
  • the generated multi-kernel source also builds against a 5.14 kernel, exercising the compatibility branch for the older NAPI helper names.

The patch is intentionally limited to XSK pool teardown and does not add device-specific behavior to the AF_XDP application.

Quiesce the datapath before unmapping an XSK pool. DQO ring reconfiguration already unregisters the old pool and starts replacement queues, so return after that operation instead of falling through and disabling NAPI on the newly created rings.

Use the locked NAPI helpers in the remaining path because XSK pool setup runs under the netdev operation lock on current kernels.

Fixes: 8c783e0 ("gve: implement DQO RX datapath and control path for AF_XDP zero-copy")
Signed-off-by: Federico Iezzi <fiezzi@google.com>
@m4r1k
m4r1k marked this pull request as ready for review August 10, 2026 21:06
@m4r1k

m4r1k commented Aug 10, 2026

Copy link
Copy Markdown
Author

Full reproducer. Run on both GCE guests deployed on c4n-highcpu-16 :

export IFACE=ens3
export QUEUES=8

sudo ethtool -K "$IFACE" rx-gro-hw off
sudo ethtool -G "$IFACE" rx-buf-len 2048 tcp-data-split off
sudo ethtool -L "$IFACE" rx "$QUEUES" tx "$QUEUES"

ethtool -i "$IFACE"
ethtool -l "$IFACE"
ethtool -g "$IFACE"
sudo dmesg | grep -E 'DQO|GVE version|queue format' | tail -20

The channel output should show:

Pre-set maximums:
RX: 16
TX: 16

Current hardware settings:
RX: 8
TX: 8

Install Wireblast v0.2.4 on both:

curl -fL \
  https://github.com/atoonk/wireblast/releases/download/v0.2.4/wireblast_linux_amd64.tar.gz \
  -o /tmp/wireblast.tar.gz

tar -xzf /tmp/wireblast.tar.gz -C /tmp
sudo install -m 0755 /tmp/wireblast /usr/local/bin/wireblast

wireblast --version

On the receiver, obtain its private address:

export IFACE=ens3
export RX_IP=$(ip -4 -o addr show dev "$IFACE" |
  awk '{split($4, address, "/"); print address[1]}')

printf 'Receiver address: %s\n' "$RX_IP"

Then start Wireblast on the receiver first:

sudo prlimit --memlock=unlimited -- \
  /usr/local/bin/wireblast \
    --no-tui \
    --yes \
    --interface ens3 \
    --mode receive \
    --rx-mode udp-port \
    --rx-port 9000 \
    --queues 8 \
    --duration 42s

On the sender, substitute the receiver’s private IP:

export RX_IP="<RECEIVER_PRIVATE_IP>"

sudo prlimit --memlock=unlimited -- \
  /usr/local/bin/wireblast \
    --no-tui \
    --yes \
    --interface ens3 \
    --mode udp \
    --packet-size 64 \
    --dst-ip "$RX_IP" \
    --dst-port 9000 \
    --flows 65535 \
    --flow-order sequential \
    --bps unlimited \
    --queues 8 \
    --duration 30s

Both processes should initially report something equivalent to: 8 queue(s), zero-copy, native XDP, driver gve

The important part of the reproducer is process termination. With the unpatched driver, AF_XDP socket teardown can cause either:

BUG: kernel NULL pointer dereference, address: 000000000000031c
RIP: gve_rx_xsk_dqo
CR2: 000000000000031c

or a Wireblast task stuck in uninterruptible sleep during the napi_disable() path. The machine may subsequently stop responding to SSH.

After Wireblast exits, inspect:

sudo dmesg -T |
  grep -E -A40 -B10 \
    'NULL pointer|gve_rx_xsk_dqo|blocked for more than|wireblast'

If the guest has already become unreachable, retrieve the serial-console output instead:

gcloud compute instances get-serial-port-output <RECEIVER_VM> \
  --zone <ZONE> \
  --port 1

Repeating the receiver-then-sender sequence three to five times makes the teardown race easier to reproduce. The UDP-port receive filter only redirects benchmark traffic, so SSH remains outside the XDP redirect rule.

@hramamurthy12

Copy link
Copy Markdown
Collaborator

CC-ing @josh8551021 to take a look.

@josh8551021

Copy link
Copy Markdown
Collaborator

Ah, I had a fix for this in the works, and was planning to send it up soon, along with a few other XDP-related fixes, once the current GVE fixes in the process of being upstramed were accepted.

For reference, these are the fixes that will be going up soon:

commit 8e32e619109dbf4e89fec35e274d87d272f9c016
Author: Joshua Washington <joshwash@google.com>
Date:   Thu Aug 6 11:36:44 2026 -0700

    gve: fix NULL dereference from premature XSK pool DMA unmap

    To ensure that XSK pools are DMA unmapped in all scenarios, GVE performs
    the unmapping before validating if the interface is up and early
    returning.

    However, if rings are up, this introduces a race between the RX NAPI and
    the control plane. As part of DMA unmapping the XSK pool, the kernel
    sets pool->dev to NULL. Because xsk_buff_dma_sync_for_cpu() relies on
    pool->dev, this results in a kernel panic:

    BUG: kernel NULL pointer dereference, address: 000000000000030c
    PGD 0
    Oops: Oops: 0000 [#1] SMP NOPTI
    CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Kdump: loaded Tainted: G           OE       6.17.0-1021-gcp #24~24.04.1-Ubuntu VOLUNTARY
    Tainted: [O]=OOT_MODULE, [E]=UNSIGNED_MODULE
    Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 09/24/2025
    RIP: 0010:gve_rx_poll_dqo+0x2e2/0x13b0 [gve]
    Code: 00 00 49 89 86 70 01 00 00 49 83 86 d0 00 00 00 01 e9 e3 00 00 00 49 8b 01 4d 8b 26 4c 01 c0 49 89 41 08 49 8b 41 60 48 8b 38 <f6> 87 0c 03 00 00 20 0f 84 97 08 00 00 4d 89 61 38 49 89 59 40 4d
    RSP: 0018:ff60da90c0003ce8 EFLAGS: 00010282
    RAX: ff1a11038c429300 RBX: ff1a11040ef1f380 RCX: 0000000000000000
    RDX: 00000000000005bb RSI: 0000000000000040 RDI: 0000000000000000
    RBP: ff60da90c0003dd0 R08: 0000000000000040 R09: ff1a1103b63cd880
    R10: 0000000000000000 R11: ff60da90d9d9d000 R12: ff1a110394607a80
    R13: ff1a11040d6d40e8 R14: ff1a1103c8558000 R15: 0000000000000000
    FS:  0000000000000000(0000) GS:ff1a112f6e635000(0000) knlGS:0000000000000000
    CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
    CR2: 000000000000030c CR3: 0000003d8fa42003 CR4: 0000000000371ef0
    Call Trace:
     <IRQ>
     gve_napi_poll_dqo+0x88/0x170 [gve]
     __napi_poll+0x30/0x210
     net_rx_action+0x210/0x410
     ? dst_destroy_rcu+0x12/0x20
     handle_softirqs+0xe4/0x310
     __irq_exit_rcu+0x10e/0x130
     irq_exit_rcu+0xe/0x20
     common_interrupt+0xb6/0xe0
     </IRQ>
     <TASK>
     asm_common_interrupt+0x27/0x40
    RIP: 0010:cpuidle_enter_state+0xda/0x6d0
    Code: 26 e7 fe e8 18 f2 ff ff 8b 53 04 49 89 c7 0f 1f 44 00 00 31 ff e8 76 2e e5 fe 80 7d d0 00 0f 85 72 01 00 00 fb 0f 1f 44 00 00 <45> 85 f6 0f 88 f1 01 00 00 4d 63 ee 49 83 fd 0a 0f 83 75 04 00 00
    RSP: 0018:ffffffff9b603d90 EFLAGS: 00000246
    RAX: 0000000000000000 RBX: ff1a112f0a63e700 RCX: 0000000000000000
    RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000
    RBP: ffffffff9b603de0 R08: 0000000000000000 R09: 0000000000000000
    R10: 0000000000000000 R11: 0000000000000000 R12: ffffffff9b90db40
    R13: 0000000000000002 R14: 0000000000000002 R15: 00000081e5679c9d
     cpuidle_enter+0x2e/0x50
     call_cpuidle+0x22/0x60
     cpuidle_idle_call+0x117/0x190
     do_idle+0x7f/0xe0
     cpu_startup_entry+0x29/0x30
     rest_init+0xc2/0xf0
     start_kernel+0x400/0x510
     ? sme_unmap_bootdata+0x14/0x80
     x86_64_start_reservations+0x18/0x30
     x86_64_start_kernel+0xfd/0x150
     ? soft_restart_cpu+0x14/0x14
     common_startup_64+0x13e/0x141
     </TASK>
    Modules linked in: gve(OE) tls 8021q garp mrp stp llc binfmt_misc nls_iso8859_1 polyval_clmulni ghash_clmulni_intel aesni_intel psmouse input_leds serio_raw sch_fq_codel dm_multipath nvme_fabrics sysfs virtio_rng ip_tables x_tables autofs4 [last unloaded: gve]
    CR2: 000000000000030c

    Leave the XSK pool DMA mapped until after rings are guaranteed to no
    longer rely on the pool.

    Fixes: d57ae093c887 ("gve: deduplicate xdp info and xsk pool registration logic")
    Cc: stable@vger.kernel.org
    Signed-off-by: Joshua Washington <joshwash@google.com>

commit 1d4ef172229efa546d2ace171d40ae66c9555e4a
Author: Joshua Washington <joshwash@google.com>
Date:   Fri Jul 24 13:02:58 2026 -0700

    gve: fix napi_disable deadlock when attemping to disable XSK pools

    When disabling XSK pools, GVE calls the unlocked versions of
    napi_disable and napi_enable. However, the netdev lock has already been
    acquired before ndo_bpf is called because GVE supports queue management
    ops. Calling the unlocked versions of napi_disable/enable results in a
    deadlock when attempting to disable XSK pools, as the thread attempts to
    re-acquire a lock it already holds.

    Update the NAPI calls to use the locked versions.

    Fixes: 606048cbd834 ("net: designate XSK pool pointers in queues as "ops protected"")
    Cc: stable@vger.kernel.org
    Signed-off-by: Joshua Washington <joshwash@google.com>

commit 2aa7b048c782cd367903f4fa4cce106e23efb9bb
Author: Joshua Washington <joshwash@google.com>
Date:   Mon Jul 20 16:09:41 2026 -0700

    gve: don't register xsk pool on pre-existing queues in RDA mode

    When XSK pools are enabled after an XDP program has already been loaded,
    XSK pools are registered on pre-existing queues before queues are
    re-created with the XSK pool fully registered in DQ RDA mode.

    This can lead to a race condition between the RX NAPI and and the
    control plane thread wherein a pre-existing queue sees the live XSK pool
    and attempts to use recycled buffers not backed by XSK buffs for AF_XDP
    ZC traffic. This causes the following kernel panic to occur when
    attempting to DMA map a NULL XSK buffer:

    [  448.703072] RIP: 0010:gve_rx_post_buffers_dqo+0x99/0x190 [gve]
    [  448.709020] Code: 84 cc 00 00 00 48 89 f0 48 2b 43 50 48 c1 f8 03 49 0f af c7 66 89 45 00 48 83 bb c0 01 00 00 00 0f 84 98 00 00 00 48 8b 46 20 <48> 8b 40 38 48 89 45 08 48 83 7b 78 00 74 17 41 0f b7 86 78 02 00
    [  448.727882] RSP: 0018:ff51132480087c88 EFLAGS: 00010286
    [  448.733204] RAX: 0000000000000000 RBX: ff23fbc168d24000 RCX: 0000000000000bd0
    [  448.740435] RDX: 0000000000000bcf RSI: ff23fbc105129548 RDI: ff23fbc168d24000
    [  448.747666] RBP: ff23fbc105cf77e0 R08: ffb78c2285a3f988 R09: 0000000000000071
    [  448.754897] R10: 0000000000000001 R11: ff23fbc1073c5090 R12: 0000000000000000
    [  448.762127] R13: 0000000000000040 R14: ff23fbc1073c5980 R15: 6db6db6db6db6db7
    [  448.769356] FS:  0000000000000000(0000) GS:ff23fbd5e7800000(0000) knlGS:0000000000000000
    [  448.777542] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
    [  448.783386] CR2: 0000000000000038 CR3: 00000006ab210002 CR4: 0000000000371eb0
    [  448.790619] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
    [  448.797856] DR3: 0000000000000000 DR6: 00000000fffe07f0 DR7: 0000000000000400
    [  448.805086] Call Trace:
    [  448.807629]  <TASK>
    [  448.809826]  gve_rx_poll_dqo+0x4d9/0xf10 [gve]
    [  448.814372]  gve_napi_poll_dqo+0x76/0x170 [gve]
    [  448.819003]  __napi_poll+0x28/0x160
    [  448.822596]  net_rx_action+0x2a0/0x350
    [  448.826442]  handle_softirqs+0xd4/0x280
    [  448.830377]  ? sort_range+0x20/0x20
    [  448.833961]  run_ksoftirqd+0x2d/0x40
    [  448.837632]  smpboot_thread_fn+0xd5/0x1d0
    [  448.841737]  kthread+0xd7/0x100
    [  448.844977]  ? kthread_complete_and_exit+0x20/0x20
    [  448.849865]  ret_from_fork+0x1f/0x30
    [  448.853538]  </TASK>
    [  448.855819] Modules linked in: gve(OE) nvme_fabrics binfmt_misc intel_rapl_msr intel_rapl_common iosf_mbi crct10dif_pclmul crc32_pclmul ghash_clmulni_intel sha512_ssse3 sha512_generic nls_ascii sha256_ssse3 nls_cp437 sha1_ssse3 vfat fat aesni_intel crypto_simd cryptd rapl pvpanic_mmio pvpanic evdev serio_raw button loop fuse efi_pstore dm_mod configfs efivarfs virtio_rng ip_tables x_tables autofs4 virtio_pci virtio_pci_legacy_dev virtio_pci_modern_dev virtio crc32c_intel virtio_ring [last unloaded: gve]
    [  448.900491] CR2: 0000000000000038

    The XSK pool should only be registered with current queues if XSK
    buffers are allocated on-the-fly, as is the case in QPL mode.

    Fixes: c1fffc5d66a7 ("gve: implement DQO RX datapath and control path for AF_XDP zero-copy")
    Cc: stable@vger.kernel.org
    Signed-off-by: Joshua Washington <joshwash@google.com>

commit 43ede8f4b24f971ee2f406e1b25e9effad98102a
Author: Joshua Washington <joshwash@google.com>
Date:   Mon Jul 20 15:28:36 2026 -0700

    gve: fix XSK buffer leak on error descriptor

    When the error bit is set in the RX completion descriptor, the buf_state
    and its attached buffer should be freed. In the case of AF_XDP ZC, the
    XSK buffer was not freed, leading to a leak.

    Fixes: c1fffc5d66a7 ("gve: implement DQO RX datapath and control path for AF_XDP zero-copy")
    Cc: stable@vger.kernel.org
    Signed-off-by: Joshua Washington <joshwash@google.com>

commit d5925f83168f15ee6c3b94261aa701da56cc2d9e
Author: Joshua Washington <joshwash@google.com>
Date:   Mon Jul 20 15:12:58 2026 -0700

    gve: fix XSK buffer leak when rings are stopped

    GVE does not free XSK buffers when resetting ring state as a part of
    stopping queues. This causes all XSK buffers which are posted to the
    NIC to be leaked.

    Free XSK buffers attached to an allocated buf_state when stopping rings.

    Fixes: c1fffc5d66a7 ("gve: implement DQO RX datapath and control path for AF_XDP zero-copy")
    Cc: stable@vger.kernel.org
    Signed-off-by: Joshua Washington <joshwash@google.com>

commit e62801f0de85a172e497f8d00d755105c8306e0e
Author: Joshua Washington <joshwash@google.com>
Date:   Thu Jul 9 15:05:55 2026 -0700

    gve: increment work_done for XDP and error packets

    The GVE RX NAPI will continue polling as long as

    1) there are packets to be processed, and
    2) less than NAPI budget SKBs (denoted in GVE by work_done) have been
       passed up to the kernel.

    However, GVE does not account for all of the packets that don't create
    SKBs, namely error packets and XDP packets.

    This can result in XDP programs that scarcely return XDP_PASS failing to
    exit the NAPI poll as long as the NIC is DMA'ing packets, possibly
    processing the entire RX ring before returning from the NAPI.

    This has 3 negative implications:

    1) XDP RX path can run much longer than is desirable, hogging CPU
       resources.
    2) If XDP_PASS is never returned, the work_done never increases beyond
       0, which can lead to scheduling delays due to missed chances to
       reschedule the NAPI.
    3) In AF_XDP zero-copy, XSK_TX occurs after the RX poll. If the RX poll
       takes a long time, it will delay TX, leading to degraded performance.

    Ensure every packet is accounted for in work_done by incrementing
    work_done before checking for the existence of a SKB.

    Fixes: 293b49361f91 ("gve: add XDP DROP and PASS support for DQ")
    Cc: stable@vger.kernel.org
    Signed-off-by: Joshua Washington <joshwash@google.com>

@m4r1k

m4r1k commented Aug 11, 2026

Copy link
Copy Markdown
Author

Thanks @josh8551021! Looking forward for the new release then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants