Fix EDNS OPT record corruption in DNS cache

The TTL computation and assignment loops treat OPT record's Hdr.Ttl
as a regular TTL, but per RFC 6891 it encodes EDNS0 metadata
(ExtRCode|Version|Flags). This corrupts cached responses causing
systemd-resolved to reject them with EDNS version 255.

Also fix pointer aliasing: storeCache() stored raw *dns.Msg pointer
so subsequent mutations by Exchange() corrupted cached data.

- Skip OPT records in all TTL loops (Exchange + loadResponse)
- Use message.Copy() in storeCache() to isolate cache from mutations
This commit is contained in:
Berkay Özdemirci
2026-04-10 09:05:07 +03:00
committed by 世界
parent 7c3d8cf8db
commit 5a957fd750

View File

@@ -283,6 +283,9 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
if timeToLive == 0 { if timeToLive == 0 {
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
for _, record := range recordList { for _, record := range recordList {
if record.Header().Rrtype == dns.TypeOPT {
continue
}
if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive { if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {
timeToLive = record.Header().Ttl timeToLive = record.Header().Ttl
} }
@@ -294,6 +297,9 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
} }
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
for _, record := range recordList { for _, record := range recordList {
if record.Header().Rrtype == dns.TypeOPT {
continue
}
record.Header().Ttl = timeToLive record.Header().Ttl = timeToLive
} }
} }
@@ -381,21 +387,21 @@ func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Questio
} }
if c.disableExpire { if c.disableExpire {
if !c.independentCache { if !c.independentCache {
c.cache.Add(question, message) c.cache.Add(question, message.Copy())
} else { } else {
c.transportCache.Add(transportCacheKey{ c.transportCache.Add(transportCacheKey{
Question: question, Question: question,
transportTag: transport.Tag(), transportTag: transport.Tag(),
}, message) }, message.Copy())
} }
} else { } else {
if !c.independentCache { if !c.independentCache {
c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive)) c.cache.AddWithLifetime(question, message.Copy(), time.Second*time.Duration(timeToLive))
} else { } else {
c.transportCache.AddWithLifetime(transportCacheKey{ c.transportCache.AddWithLifetime(transportCacheKey{
Question: question, Question: question,
transportTag: transport.Tag(), transportTag: transport.Tag(),
}, message, time.Second*time.Duration(timeToLive)) }, message.Copy(), time.Second*time.Duration(timeToLive))
} }
} }
} }
@@ -486,6 +492,9 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
var originTTL int var originTTL int
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
for _, record := range recordList { for _, record := range recordList {
if record.Header().Rrtype == dns.TypeOPT {
continue
}
if originTTL == 0 || record.Header().Ttl > 0 && int(record.Header().Ttl) < originTTL { if originTTL == 0 || record.Header().Ttl > 0 && int(record.Header().Ttl) < originTTL {
originTTL = int(record.Header().Ttl) originTTL = int(record.Header().Ttl)
} }
@@ -500,12 +509,18 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
duration := uint32(originTTL - nowTTL) duration := uint32(originTTL - nowTTL)
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
for _, record := range recordList { for _, record := range recordList {
if record.Header().Rrtype == dns.TypeOPT {
continue
}
record.Header().Ttl = record.Header().Ttl - duration record.Header().Ttl = record.Header().Ttl - duration
} }
} }
} else { } else {
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} { for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
for _, record := range recordList { for _, record := range recordList {
if record.Header().Rrtype == dns.TypeOPT {
continue
}
record.Header().Ttl = uint32(nowTTL) record.Header().Ttl = uint32(nowTTL)
} }
} }