Merge commit from fork

EndpointSlice FQDN addresses were copied into endpoint records as if
they were IP addresses. CoreDNS then treated each value as a CNAME
target. A tenant could build a branching chain of headless Services that
made one query perform exponential work and exhaust CoreDNS memory.

Publish endpoints only from IPv4 and IPv6 slices and validate every
address against its declared family. Deduplicate CNAME targets for the
whole query so repeated or converging paths are chased once.

Protect internal self-lookups with an independent depth limit. This
stops direct and indirect k8s_external CNAME loops without consuming the
loop counter already maintained by the file and rewrite plugins.

Add regression coverage for address types, recursive convergence,
repeated k8s_external targets, and direct and indirect self-reference.

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
This commit is contained in:
Ville Vesilehto
2026-10-25 04:45:58 +02:00
committed by GitHub
parent a5fdd11fcf
commit 4ec856f347
10 changed files with 548 additions and 0 deletions

View File

@@ -15,6 +15,27 @@ import (
const maxCnameChainLength = 10
type cnameVisitKey struct{}
func cnameVisits(ctx context.Context) (context.Context, map[string]struct{}) {
if visited, ok := ctx.Value(cnameVisitKey{}).(map[string]struct{}); ok {
return ctx, visited
}
visited := make(map[string]struct{})
return context.WithValue(ctx, cnameVisitKey{}, visited), visited
}
// firstCNAMEVisit reports whether target has not already been chased while
// answering the current client query.
func firstCNAMEVisit(visited map[string]struct{}, target string) bool {
target = dns.CanonicalName(target)
if _, ok := visited[target]; ok {
return false
}
visited[target] = struct{}{}
return true
}
// A returns A records from Backend or an error.
func A(ctx context.Context, b ServiceBackend, zone string, state request.Request, previousRecords []dns.RR, opt Options) (records []dns.RR, truncated bool, err error) {
services, err := checkForApex(ctx, b, zone, state, opt)
@@ -23,6 +44,7 @@ func A(ctx context.Context, b ServiceBackend, zone string, state request.Request
}
dup := make(map[string]struct{})
var cnames map[string]struct{}
for _, serv := range services {
what, ip := serv.HostType()
@@ -43,6 +65,12 @@ func A(ctx context.Context, b ServiceBackend, zone string, state request.Request
if dnsutil.DuplicateCNAME(newRecord, previousRecords) {
continue
}
if cnames == nil {
ctx, cnames = cnameVisits(ctx)
}
if !firstCNAMEVisit(cnames, newRecord.Target) {
continue
}
if dns.IsSubDomain(zone, dns.Fqdn(serv.Host)) {
state1 := state.NewWithQuestion(serv.Host, state.QType())
state1.Zone = zone
@@ -96,6 +124,7 @@ func AAAA(ctx context.Context, b ServiceBackend, zone string, state request.Requ
}
dup := make(map[string]struct{})
var cnames map[string]struct{}
for _, serv := range services {
what, ip := serv.HostType()
@@ -117,6 +146,12 @@ func AAAA(ctx context.Context, b ServiceBackend, zone string, state request.Requ
if dnsutil.DuplicateCNAME(newRecord, previousRecords) {
continue
}
if cnames == nil {
ctx, cnames = cnameVisits(ctx)
}
if !firstCNAMEVisit(cnames, newRecord.Target) {
continue
}
if dns.IsSubDomain(zone, dns.Fqdn(serv.Host)) {
state1 := state.NewWithQuestion(serv.Host, state.QType())
state1.Zone = zone
@@ -350,6 +385,7 @@ func TXT(ctx context.Context, b ServiceBackend, zone string, state request.Reque
}
dup := make(map[string]struct{})
var cnames map[string]struct{}
for _, serv := range services {
what, _ := serv.HostType()
@@ -370,6 +406,12 @@ func TXT(ctx context.Context, b ServiceBackend, zone string, state request.Reque
if dnsutil.DuplicateCNAME(newRecord, previousRecords) {
continue
}
if cnames == nil {
ctx, cnames = cnameVisits(ctx)
}
if !firstCNAMEVisit(cnames, newRecord.Target) {
continue
}
if dns.IsSubDomain(zone, dns.Fqdn(serv.Host)) {
state1 := state.NewWithQuestion(serv.Host, state.QType())
state1.Zone = zone