From 0ed3aae547ac598bbed860fbd951d29702400811 Mon Sep 17 00:00:00 2001 From: rpb-ant Date: Sat, 11 Apr 2026 04:39:31 -0400 Subject: [PATCH] Fix data race in xfr.go (#8039) Signed-off-by: Ryan Brewster --- plugin/file/xfr.go | 10 +++++++--- plugin/file/zone.go | 5 +++++ test/secondary_test.go | 4 ++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/plugin/file/xfr.go b/plugin/file/xfr.go index eab880c33..75e202d48 100644 --- a/plugin/file/xfr.go +++ b/plugin/file/xfr.go @@ -19,8 +19,12 @@ func (f File) Transfer(zone string, serial uint32) (<-chan []dns.RR, error) { // Transfer transfers a zone with serial in the returned channel and implements IXFR fallback, by just // sending a single SOA record. func (z *Zone) Transfer(serial uint32) (<-chan []dns.RR, error) { - // get soa and apex - apex, err := z.ApexIfDefined() + // Snapshot apex and tree under one read lock so the goroutine walks the + // same generation the SOA came from, even if TransferIn swaps them mid-AXFR. + z.RLock() + apex, err := z.apexIfDefinedLocked() + t := z.Tree + z.RUnlock() if err != nil { return nil, err } @@ -35,7 +39,7 @@ func (z *Zone) Transfer(serial uint32) (<-chan []dns.RR, error) { } ch <- apex - z.Walk(func(e *tree.Elem, _ map[uint16][]dns.RR) error { ch <- e.All(); return nil }) + t.Walk(func(e *tree.Elem, _ map[uint16][]dns.RR) error { ch <- e.All(); return nil }) ch <- []dns.RR{apex[0]} close(ch) diff --git a/plugin/file/zone.go b/plugin/file/zone.go index 9bcf4cc42..d0f792c34 100644 --- a/plugin/file/zone.go +++ b/plugin/file/zone.go @@ -146,6 +146,11 @@ func (z *Zone) SetFile(path string) { func (z *Zone) ApexIfDefined() ([]dns.RR, error) { z.RLock() defer z.RUnlock() + return z.apexIfDefinedLocked() +} + +// apexIfDefinedLocked is ApexIfDefined without locking; caller must hold z's read lock. +func (z *Zone) apexIfDefinedLocked() ([]dns.RR, error) { if z.SOA == nil { return nil, fmt.Errorf("no SOA") } diff --git a/test/secondary_test.go b/test/secondary_test.go index 8f03b1db0..39f32ad9a 100644 --- a/test/secondary_test.go +++ b/test/secondary_test.go @@ -290,7 +290,7 @@ func TestSecondaryZoneNotify(t *testing.T) { if len(r.Answer) != 0 { break } - time.Sleep(1000 * time.Microsecond) + time.Sleep(100 * time.Millisecond) } if len(r.Answer) == 0 { t.Fatalf("Expected answer section") @@ -323,7 +323,7 @@ www IN A 127.0.0.1 if len(r.Answer) != 0 { break } - time.Sleep(1000 * time.Microsecond) + time.Sleep(100 * time.Millisecond) } if len(r.Answer) != 1 { t.Fatalf("Expected one RR in answer section got %d", len(r.Answer))