GitHub Advisoryadvisory · Go

GHSA-74w3-63xv-x9mv CoreDNS `k8s_external` headless AXFR can emit an empty transfer batch that panics the `transfer` plugin

LOW Published 2026-10-16 Modified 2026-10-16 Official page ↗
Aliases
CVE-2026-62994
Ecosystem
Go
Packages
github.com/coredns/coredns
Severity
Low
CVSS vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L
Weaknesses
CWE-248, CWE-755
Reviewed
2026-10-16

Affected packages

• Go github.com/coredns/coredns: >= 1.9.4, < 1.14.5 → fixed in 1.14.5

Description

Summary A network DNS client allowed to request AXFR for a CoreDNS zone can trigger a panic when CoreDNS is configured with `k8s_external` headless-service zone transfers and Kubernetes contains a headless service endpoint with no declared ports. The built-in `k8s_external` transferer can emit an empty `[]dns.RR` batch, and the shared `transfer` plugin indexes `records[0]` without checking that the batch is non-empty. In normal CoreDNS mode the panic is recovered and returned as `SERVFAIL`, but repeated triggering can disrupt requests and amplify panic logging; if the `debug` plugin disables recovery, the same path can crash the CoreDNS process. Details `plugin/transfer/transfer.go` assumes that every `Transferer` emits only non-empty `[]dns.RR` batches. During AXFR/IXFR response generation it indexes `records[0]` without checking the slice length: • `plugin/transfer/transfer.go:129-131` ```go for records := range pchan { if x, ok := records[0].(*dns.SOA); ok && soa == nil { soa = x } ``` The built-in `k8s_external` transferer violates that contract for headless services whose endpoints have no declared ports. `plugin/k8s_external/transfer.go` sends the SRV result slice unconditionally: • `plugin/k8s_external/transfer.go:104-106` ```go recs, _ := e.srv(ctx, svcs, s) ch 0` in the headless endpoint and port/protocol paths. 3. Add regression tests covering `Port: -1` headless services and empty transfer batches. Example consumer-side guard: ```go for records := range pchan { if len(records) == 0 { continue } if x, ok := records[0].(*dns.SOA); ok && soa == nil { soa = x } ... } ``` PoC Safe local unit-level reproduction was performed. It does not contact external infrastructure and does not require a live Kubernetes cluster. Reproducer A: the `transfer` sink panics on an empty batch A temporary package test was added under `plugin/transfer`: ```go package transfer import ( "context" "fmt" "testing" "github.com/coredns/coredns/plugin/pkg/dnstest" "github.com/coredns/coredns/plugin/test" "github.com/miekg/dns" ) type auditEmptyBatchTransferer struct{} func (auditEmptyBatchTransferer) Transfer(zone string, serial uint32) (<-chan []dns.RR, error) { if zone != "example.org." { return nil, ErrNotAuthoritative } ch := make(chan []dns.RR, 3) ch <- []dns.RR{test.SOA(fmt.Sprintf("%s 100 IN SOA ns.dns.%s hostmaster.%s %d 7200 1800 86400 100", zone, zone, zone, 1))} ch <- []dns.RR{} close(ch) return ch, nil } func TestAuditEmptyTransferBatchPanics(t *testing.T) { tr := &Transfer{ Transferers: []Transferer{auditEmptyBatchTransferer{}}, xfrs: []*xfr{{Zones: []string{"example.org."}, to: []string{"*"}}}, } req := new(dns.Msg) req.SetQuestion("example.org.", dns.TypeAXFR) rec := dnstest.NewRecorder(&test.ResponseWriter{TCP: true}) defer func() { if rec := recover(); rec == nil { t.Fatal("expected empty transfer batch to panic") } }() _, _ = tr.ServeDNS(context.Background(), rec, req) } ``` Verification command and result: ```text $ go test ./plugin/transfer -run TestAuditEmptyTransferBatchPanics -count=1 -v === RUN TestAuditEmptyTransferBatchPanics --- PASS: TestAuditEmptyTransferBatchPanics (0.00s) PASS ok github.com/coredns/coredns/plugin/transfer 0.005s ``` Reproducer B: the built-in `k8s_external` transferer emits the empty batch A temporary package test was added under `plugin/k8s_external`: ```go package external import ( "testing" "time" "github.com/coredns/coredns/plugin/etcd/msg" "github.com/coredns/coredns/plugin/kubernetes" "github.com/coredns/coredns/request" "github.com/miekg/dns" ) func TestAuditHeadlessNoPortTransferEmitsEmptyBatch(t *testing.T) { e := New() e.Zones = []string{"example.com."} e.headless = true e.externalAddrFunc = func(request.Request, bool) []dns.RR { return nil } e.externalSerialFunc = func(string) uint32 { return 1 } e.externalServicesFunc = func(string, bool) ([]msg.Service, map[string][]msg.Service) { return nil, map[string][]msg.Service{ "/coredns/com/example/testns/headless/" + kubernetes.Endpoint: {{Host: "192.0.2.10", Port: -1, TTL: 5, Key: "/coredns/com/example/testns/headless/endpoint-a"}}, } } ch, err := e.Transfer("example.com.", 0) if err != nil { t.Fatalf("Transfer returned error: %v", err) } deadline := time.After(time.Second) for { select { case batch, ok := <-ch: if !ok { t.Fatal("transfer closed before emitting an empty batch") } if len(batch) == 0 { return } case <-deadline: t.Fatal("timed out waiting for an empty batch") } } } ``` Verification command and result: ```text $ go test ./plugin/k8s_external -run TestAuditHeadlessNoPortTransferEmitsEmptyBatch -count=1 -v === RUN TestAuditHeadlessNoPortTransferEmitsEmptyBatch --- PASS: TestAuditHeadlessNoPortTransferEmitsEmptyBatch (0.00s) PASS ok github.com/coredns/coredns/plugin/k8s_external 0.012s ``` Additional focused verification: ```text $ go test ./plugin/transfer ./plugin/k8s_external ./plugin/kubernetes -run 'TestAudit|^$' -count=1 -v ok github.com/coredns/coredns/plugin/transfer 0.006s ok github.com/coredns/coredns/plugin/k8s_external 0.013s ok github.com/coredns/coredns/plugin/kubernetes 0.116s ``` Affected package suites: ```text $ go test ./plugin/transfer ./plugin/k8s_external ./plugin/kubernetes -count=1 ok github.com/coredns/coredns/plugin/transfer 0.010s ok github.com/coredns/coredns/plugin/k8s_external 0.011s ok github.com/coredns/coredns/plugin/kubernetes 0.194s ``` Impact A network client allowed to request AXFR for the affected zone can trigger a panic in CoreDNS when all prerequisites are present. Required configuration/state: ```corefile example.com:53 { kubernetes cluster.local k8s_external example.com { headless } transfer example.com { to * } } ``` Additional prerequisite: • The Kubernetes API state contains a headless-service endpoint represented by an EndpointSlice with ready addresses and no ports, producing CoreDNS `msg.Service{Port: -1}`. In normal CoreDNS mode, `core/dnsserver/server.go:267-279` recovers the panic and returns `SERVFAIL`, so this is a request-level availability issue and panic/log amplification issue. If the `debug` plugin is enabled, the server disables the recovery path and this can crash the CoreDNS process.

Related

Alias · CVE (1)

Weakness · CWE (2)

  • CWE-248 Uncaught Exception weakness
  • CWE-755 Improper Handling of Exceptional Conditions weakness

References