Re: [PATCH net] net: neighbour: Serialize proxy timer teardown
From: Runyu Xiao
Date: Mon Sep 21 2026 - 02:27:45 EST
Hi,
Thanks for reviewing this.
On Mon, 14 Sep 2026, Runyu Xiao wrote:
> A controlled QEMU test with explicit 5 ms schedule injection reproduced
> the orphaned queue state on the unfixed kernel and did not reproduce it
> with this change. Natural-load runs reached both paths but did not
> trigger the narrow race.
Could you provide the repro ?
The reproducer uses an x86_64 QEMU guest with three network namespaces and
veth pairs. Proxy ARP and IP forwarding are enabled on the guest, and an
AF_PACKET helper sends valid broadcast ARP requests from a source namespace
through the proxy-ARP ingress interface. The same interface is repeatedly
brought down and up while the requests are being sent.
For the deterministic run, I built the baseline and fixed kernels from the
same source and temporarily enabled C0109_POC instrumentation. It adds a
5 ms delay at both of these points:
- immediately before arp_process() calls pneigh_enqueue();
- after __neigh_ifdown() observes an empty proxy queue and immediately
before timer_delete_sync().
The guest was run with two vCPUs and 2 GiB of memory. The trigger performed
300 down/up cycles while sending the ARP requests. The test-only oracle
reported a failure when __neigh_ifdown() completed with proxy_queue non-empty
and proxy_timer not pending.
With the unfixed kernel, the run reached both paths and reported the oracle
772 times. With the fixed kernel, the same workload reached both paths but
reported the oracle zero times. The natural-load runs also reached both
paths, but did not hit the narrow interleaving without the temporary delay.
The delay is only schedule amplification for this race; it is not a claim
that a third-party workload triggers it frequently in normal operation.
> Isn't is resolved by simply checking if dev is down here ?
A check of the device state in pneigh_enqueue() alone does not serialize the
queue insertion with the purge, empty check, and timer deletion. An enqueue
can pass that check while the device is still up, then be delayed until
after __neigh_ifdown() has observed an empty queue and before it deletes the
timer. The newly inserted skb would still be left without a timer.
There is also a second teardown path to consider. __neigh_ifdown() is used
by neigh_carrier_down(), where the device can remain administratively up and
netif_running() can still be true. The proxy_timer_lock serializes both the
per-device and carrier-down teardown paths with proxy queue insertion and
timer rearming. The timer callback does not take this lock, so
timer_delete_sync() can still wait for an in-flight callback without a lock
dependency.
I will include a shorter version of these reproduction details in v2's
commit message and can provide the complete QEMU harness if needed.
Regards,
Runyu