From 47eafe94600db5a455287644c779c3d7ee40f6f2 Mon Sep 17 00:00:00 2001 From: jin Date: Wed, 10 Sep 2025 09:54:37 +0800 Subject: [PATCH] fix CVE-2025-58063 --- CVE-2025-58063.patch | 524 +++++++++++++++++++++++++++++++++++++++++++ coredns.spec | 9 +- 2 files changed, 531 insertions(+), 2 deletions(-) create mode 100644 CVE-2025-58063.patch diff --git a/CVE-2025-58063.patch b/CVE-2025-58063.patch new file mode 100644 index 0000000..cb4c40e --- /dev/null +++ b/CVE-2025-58063.patch @@ -0,0 +1,524 @@ +From e1768a5d272e9da649dfb8588595e5c6e4e640bf Mon Sep 17 00:00:00 2001 +From: Ville Vesilehto +Date: Fri, 5 Sep 2025 03:14:27 +0300 +Subject: [PATCH] Merge commit from fork + +Instead of casting lease ID to uint32, fix the TTL() function +to use etcd time-to-live API for determining TTL. Add configurable +min-lease-ttl and max-lease-ttl options to prevent extreme TTL +values. By default, lease records now go through bounds checking +with 30s to 1d as the min/max. + +Added unit tests for validation and docs. + +Signed-off-by: Ville Vesilehto +--- + man/coredns-etcd.7 | 12 +++- + plugin/etcd/README.md | 4 ++ + plugin/etcd/etcd.go | 57 +++++++++++++++---- + plugin/etcd/setup.go | 60 +++++++++++++++++++- + plugin/etcd/setup_test.go | 103 +++++++++++++++++++++++++++++++++++ + plugin/etcd/ttl_test.go | 112 ++++++++++++++++++++++++++++++++++++++ + 6 files changed, 333 insertions(+), 15 deletions(-) + create mode 100644 plugin/etcd/ttl_test.go + +diff --git a/man/coredns-etcd.7 b/man/coredns-etcd.7 +index 371f81f2b..f3484adde 100644 +--- a/man/coredns-etcd.7 ++++ b/man/coredns-etcd.7 +@@ -1,5 +1,5 @@ + .\" Generated by Mmark Markdown Processer - mmark.miek.nl +-.TH "COREDNS-ETCD" 7 "March 2021" "CoreDNS" "CoreDNS Plugins" ++.TH "COREDNS-ETCD" 7 "August 2025" "CoreDNS" "CoreDNS Plugins" + + .SH "NAME" + .PP +@@ -85,6 +85,10 @@ file - if the server certificate is not signed by a system-installed CA and clie + is needed. + + .RE ++.IP \(bu 4 ++\fB\fCmin-lease-ttl\fR the minimum TTL for DNS records based on etcd lease duration. Accepts flexible time formats like '30', '30s', '5m', '1h', '2h30m'. Default: 30 seconds. ++.IP \(bu 4 ++\fB\fCmax-lease-ttl\fR the maximum TTL for DNS records based on etcd lease duration. Accepts flexible time formats like '30', '30s', '5m', '1h', '2h30m'. Default: 24 hours. + + + .SH "SPECIAL BEHAVIOUR" +@@ -93,7 +97,7 @@ The \fIetcd\fP plugin leverages directory structure to look for related entries. + an entry \fB\fC/skydns/test/skydns/mx\fR would have entries like \fB\fC/skydns/test/skydns/mx/a\fR, + \fB\fC/skydns/test/skydns/mx/b\fR and so on. Similarly a directory \fB\fC/skydns/test/skydns/mx1\fR will have all + \fB\fCmx1\fR entries. Note this plugin will search through the entire (sub)tree for records. In case of the +-first example, a query for \fB\fCmx.skydns.text\fR will return both the contents of the \fB\fCa\fR and \fB\fCb\fR records. ++first example, a query for \fB\fCmx.skydns.test\fR will return both the contents of the \fB\fCa\fR and \fB\fCb\fR records. + If the directory extends deeper those records are returned as well. + + .PP +@@ -120,6 +124,8 @@ skydns.local { + etcd { + path /skydns + endpoint http://localhost:2379 ++ min\-lease\-ttl 60 # minimum 1 minute for lease\-based records ++ max\-lease\-ttl 1h # maximum 1 hour for lease\-based records + } + prometheus + cache +@@ -349,6 +355,7 @@ If you would like to use \fB\fCTXT\fR records, you can set the following: + + .nf + % etcdctl put /skydns/local/skydns/x6 '{"ttl":60,"text":"this is a random text message."}' ++% etcdctl put /skydns/local/skydns/x7 '{"ttl":60,"text":"this is a another random text message."}' + + .fi + .RE +@@ -362,6 +369,7 @@ If you query the zone name for \fB\fCTXT\fR now, you will get the following resp + .nf + % dig +short skydns.local TXT @localhost + "this is a random text message." ++"this is a another random text message." + + .fi + .RE +diff --git a/plugin/etcd/README.md b/plugin/etcd/README.md +index 0c7f1ea33..f01a66931 100644 +--- a/plugin/etcd/README.md ++++ b/plugin/etcd/README.md +@@ -55,6 +55,8 @@ etcd [ZONES...] { + * three arguments - path to cert PEM file, path to client private key PEM file, path to CA PEM + file - if the server certificate is not signed by a system-installed CA and client certificate + is needed. ++* `min-lease-ttl` the minimum TTL for DNS records based on etcd lease duration. Accepts flexible time formats like '30', '30s', '5m', '1h', '2h30m'. Default: 30 seconds. ++* `max-lease-ttl` the maximum TTL for DNS records based on etcd lease duration. Accepts flexible time formats like '30', '30s', '5m', '1h', '2h30m'. Default: 24 hours. + + ## Special Behaviour + +@@ -83,6 +85,8 @@ skydns.local { + etcd { + path /skydns + endpoint http://localhost:2379 ++ min-lease-ttl 60 # minimum 1 minute for lease-based records ++ max-lease-ttl 1h # maximum 1 hour for lease-based records + } + prometheus + cache +diff --git a/plugin/etcd/etcd.go b/plugin/etcd/etcd.go +index a78673027..b311def1b 100644 +--- a/plugin/etcd/etcd.go ++++ b/plugin/etcd/etcd.go +@@ -21,21 +21,25 @@ import ( + ) + + const ( +- priority = 10 // default priority when nothing is set +- ttl = 300 // default ttl when nothing is set +- etcdTimeout = 5 * time.Second ++ defaultPriority = 10 // default priority when nothing is set ++ defaultTTL = 300 // default ttl when nothing is set ++ defaultLeaseMinTTL = 30 // default minimum TTL for lease-based records ++ defaultLeaseMaxTTL = 86400 // default maximum TTL for lease-based records ++ etcdTimeout = 5 * time.Second + ) + + var errKeyNotFound = errors.New("key not found") + + // Etcd is a plugin talks to an etcd cluster. + type Etcd struct { +- Next plugin.Handler +- Fall fall.F +- Zones []string +- PathPrefix string +- Upstream *upstream.Upstream +- Client *etcdcv3.Client ++ Next plugin.Handler ++ Fall fall.F ++ Zones []string ++ PathPrefix string ++ Upstream *upstream.Upstream ++ Client *etcdcv3.Client ++ MinLeaseTTL uint32 // minimum TTL for lease-based records ++ MaxLeaseTTL uint32 // maximum TTL for lease-based records + + endpoints []string // Stored here as well, to aid in testing. + } +@@ -146,7 +150,7 @@ Nodes: + + serv.TTL = e.TTL(n, serv) + if serv.Priority == 0 { +- serv.Priority = priority ++ serv.Priority = defaultPriority + } + + if shouldInclude(serv, qType) { +@@ -159,10 +163,39 @@ Nodes: + // TTL returns the smaller of the etcd TTL and the service's + // TTL. If neither of these are set (have a zero value), a default is used. + func (e *Etcd) TTL(kv *mvccpb.KeyValue, serv *msg.Service) uint32 { +- etcdTTL := uint32(kv.Lease) ++ var etcdTTL uint32 ++ ++ // Get actual lease TTL from etcd if lease exists and client is available ++ if kv.Lease != 0 && e.Client != nil { ++ if resp, err := e.Client.TimeToLive(context.Background(), etcdcv3.LeaseID(kv.Lease)); err == nil && resp.TTL > 0 { ++ leaseTTL := resp.TTL ++ ++ // Get bounds with defaults ++ minTTL := e.MinLeaseTTL ++ if minTTL == 0 { ++ minTTL = defaultLeaseMinTTL ++ } ++ maxTTL := e.MaxLeaseTTL ++ if maxTTL == 0 { ++ maxTTL = defaultLeaseMaxTTL ++ } ++ ++ // Clamp lease TTL to configured bounds ++ minTTL64 := int64(minTTL) ++ maxTTL64 := int64(maxTTL) ++ ++ if leaseTTL < minTTL64 { ++ leaseTTL = minTTL64 ++ } else if leaseTTL > maxTTL64 { ++ leaseTTL = maxTTL64 ++ } ++ ++ etcdTTL = uint32(leaseTTL) ++ } ++ } + + if etcdTTL == 0 && serv.TTL == 0 { +- return ttl ++ return defaultTTL + } + if etcdTTL == 0 { + return serv.TTL +diff --git a/plugin/etcd/setup.go b/plugin/etcd/setup.go +index 68a1b7f42..2ddbf4597 100644 +--- a/plugin/etcd/setup.go ++++ b/plugin/etcd/setup.go +@@ -2,7 +2,11 @@ package etcd + + import ( + "crypto/tls" ++ "errors" + "path/filepath" ++ "strconv" ++ "strings" ++ "time" + + "github.com/coredns/caddy" + "github.com/coredns/coredns/core/dnsserver" +@@ -33,7 +37,11 @@ func setup(c *caddy.Controller) error { + + func etcdParse(c *caddy.Controller) (*Etcd, error) { + config := dnsserver.GetConfig(c) +- etc := Etcd{PathPrefix: "skydns"} ++ etc := Etcd{ ++ PathPrefix: "skydns", ++ MinLeaseTTL: defaultLeaseMinTTL, ++ MaxLeaseTTL: defaultLeaseMaxTTL, ++ } + var ( + tlsConfig *tls.Config + err error +@@ -88,6 +96,24 @@ func etcdParse(c *caddy.Controller) (*Etcd, error) { + return &Etcd{}, c.Errf("credentials requires 2 arguments, username and password") + } + username, password = args[0], args[1] ++ case "min-lease-ttl": ++ if !c.NextArg() { ++ return &Etcd{}, c.ArgErr() ++ } ++ minLeaseTTL, err := parseTTL(c.Val()) ++ if err != nil { ++ return &Etcd{}, c.Errf("invalid min-lease-ttl value: %v", err) ++ } ++ etc.MinLeaseTTL = minLeaseTTL ++ case "max-lease-ttl": ++ if !c.NextArg() { ++ return &Etcd{}, c.ArgErr() ++ } ++ maxLeaseTTL, err := parseTTL(c.Val()) ++ if err != nil { ++ return &Etcd{}, c.Errf("invalid max-lease-ttl value: %v", err) ++ } ++ etc.MaxLeaseTTL = maxLeaseTTL + default: + if c.Val() != "}" { + return &Etcd{}, c.Errf("unknown property '%s'", c.Val()) +@@ -124,3 +150,35 @@ func newEtcdClient(endpoints []string, cc *tls.Config, username, password string + } + + const defaultEndpoint = "http://localhost:2379" ++ ++// parseTTL parses a TTL value with flexible time units using Go's standard duration parsing. ++// Supports formats like: "30", "30s", "5m", "1h", "90s", "2h30m", etc. ++func parseTTL(s string) (uint32, error) { ++ s = strings.TrimSpace(s) ++ if s == "" { ++ return 0, nil ++ } ++ ++ // Handle plain numbers (assume seconds) ++ if _, err := strconv.ParseUint(s, 10, 64); err == nil { ++ // If it's just a number, append "s" for seconds ++ s += "s" ++ } ++ ++ // Use Go's standard time.ParseDuration for robust parsing ++ duration, err := time.ParseDuration(s) ++ if err != nil { ++ return 0, errors.New("invalid TTL format, use format like '30', '30s', '5m', '1h', or '2h30m'") ++ } ++ ++ // Convert to seconds and check bounds ++ seconds := duration.Seconds() ++ if seconds < 0 { ++ return 0, errors.New("TTL must be non-negative") ++ } ++ if seconds > 4294967295 { // uint32 max value ++ return 0, errors.New("TTL too large, maximum is 4294967295 seconds") ++ } ++ ++ return uint32(seconds), nil ++} +diff --git a/plugin/etcd/setup_test.go b/plugin/etcd/setup_test.go +index 4922641bb..c88dd1044 100644 +--- a/plugin/etcd/setup_test.go ++++ b/plugin/etcd/setup_test.go +@@ -66,6 +66,31 @@ func TestSetupEtcd(t *testing.T) { + } + `, true, "skydns", []string{"http://localhost:2379"}, "Wrong argument count", "", "", + }, ++ // with custom min-lease-ttl ++ { ++ `etcd { ++ endpoint http://localhost:2379 ++ min-lease-ttl 60 ++ } ++ `, false, "skydns", []string{"http://localhost:2379"}, "", "", "", ++ }, ++ // with custom max-lease-ttl ++ { ++ `etcd { ++ endpoint http://localhost:2379 ++ max-lease-ttl 1h ++ } ++ `, false, "skydns", []string{"http://localhost:2379"}, "", "", "", ++ }, ++ // with both custom min-lease-ttl and max-lease-ttl ++ { ++ `etcd { ++ endpoint http://localhost:2379 ++ min-lease-ttl 120 ++ max-lease-ttl 7200 ++ } ++ `, false, "skydns", []string{"http://localhost:2379"}, "", "", "", ++ }, + } + + for i, test := range tests { +@@ -113,6 +138,84 @@ func TestSetupEtcd(t *testing.T) { + t.Errorf("Etcd password not correctly set for input %s. Expected: '%+v', actual: '%+v'", test.input, test.password, etcd.Client.Password) + } + } ++ ++ // Check TTL configuration for specific test cases ++ if strings.Contains(test.input, "min-lease-ttl 60") { ++ if etcd.MinLeaseTTL != 60 { ++ t.Errorf("MinLeaseTTL not set correctly for input %s. Expected: 60, actual: %d", test.input, etcd.MinLeaseTTL) ++ } ++ } ++ if strings.Contains(test.input, "max-lease-ttl 1h") { ++ if etcd.MaxLeaseTTL != 3600 { ++ t.Errorf("MaxLeaseTTL not set correctly for input %s. Expected: 3600, actual: %d", test.input, etcd.MaxLeaseTTL) ++ } ++ } ++ if strings.Contains(test.input, "min-lease-ttl 120") && strings.Contains(test.input, "max-lease-ttl 7200") { ++ if etcd.MinLeaseTTL != 120 { ++ t.Errorf("MinLeaseTTL not set correctly for input %s. Expected: 120, actual: %d", test.input, etcd.MinLeaseTTL) ++ } ++ if etcd.MaxLeaseTTL != 7200 { ++ t.Errorf("MaxLeaseTTL not set correctly for input %s. Expected: 7200, actual: %d", test.input, etcd.MaxLeaseTTL) ++ } ++ } + } + } + } ++ ++func TestParseTTL(t *testing.T) { ++ tests := []struct { ++ input string ++ expected uint32 ++ hasError bool ++ desc string ++ }{ ++ // Plain numbers (assumed to be seconds) ++ {"30", 30, false, "plain number should be treated as seconds"}, ++ {"300", 300, false, "plain number should be treated as seconds"}, ++ ++ // Explicit seconds ++ {"30s", 30, false, "explicit seconds"}, ++ {"90s", 90, false, "explicit seconds"}, ++ ++ // Minutes ++ {"5m", 300, false, "5 minutes"}, ++ {"1m", 60, false, "1 minute"}, ++ ++ // Hours ++ {"1h", 3600, false, "1 hour"}, ++ {"2h", 7200, false, "2 hours"}, ++ ++ // Complex durations (Go's ParseDuration supports this) ++ {"2h30m", 9000, false, "2 hours 30 minutes"}, ++ {"1h30m45s", 5445, false, "1 hour 30 minutes 45 seconds"}, ++ ++ // Edge cases ++ {"0", 0, false, "zero should be allowed"}, ++ {"0s", 0, false, "zero seconds should be allowed"}, ++ {"", 0, false, "empty string should return 0"}, ++ ++ // Error cases ++ {"-30s", 0, true, "negative duration should error"}, ++ {"abc", 0, true, "invalid format should error"}, ++ {"1y", 0, true, "unsupported unit should error"}, ++ } ++ ++ for _, tt := range tests { ++ t.Run(tt.desc, func(t *testing.T) { ++ result, err := parseTTL(tt.input) ++ ++ if tt.hasError { ++ if err == nil { ++ t.Errorf("parseTTL(%q) expected error but got none", tt.input) ++ } ++ } else { ++ if err != nil { ++ t.Errorf("parseTTL(%q) unexpected error: %v", tt.input, err) ++ } ++ if result != tt.expected { ++ t.Errorf("parseTTL(%q) = %d, expected %d", tt.input, result, tt.expected) ++ } ++ } ++ }) ++ } ++} +diff --git a/plugin/etcd/ttl_test.go b/plugin/etcd/ttl_test.go +new file mode 100644 +index 000000000..6dd346bb7 +--- /dev/null ++++ b/plugin/etcd/ttl_test.go +@@ -0,0 +1,112 @@ ++package etcd ++ ++import ( ++ "testing" ++ ++ "github.com/coredns/coredns/plugin/etcd/msg" ++ "go.etcd.io/etcd/api/v3/mvccpb" ++) ++ ++func TestTTL(t *testing.T) { ++ tests := []struct { ++ name string ++ leaseID int64 ++ serviceTTL uint32 ++ minLeaseTTL uint32 ++ maxLeaseTTL uint32 ++ hasClient bool ++ expectedTTL uint32 ++ }{ ++ { ++ name: "no client, large lease ID falls back to default", ++ leaseID: 0x12345678FFFFFFFF, // Large lease ID that would cause issues ++ serviceTTL: 0, ++ minLeaseTTL: 0, ++ maxLeaseTTL: 0, ++ hasClient: false, ++ expectedTTL: defaultTTL, ++ }, ++ { ++ name: "no client, zero lease ID falls back to default", ++ leaseID: 0, ++ serviceTTL: 0, ++ minLeaseTTL: 0, ++ maxLeaseTTL: 0, ++ hasClient: false, ++ expectedTTL: defaultTTL, ++ }, ++ { ++ name: "no client, service TTL takes precedence", ++ leaseID: 120, ++ serviceTTL: 300, ++ minLeaseTTL: 0, ++ maxLeaseTTL: 0, ++ hasClient: false, ++ expectedTTL: 300, ++ }, ++ { ++ name: "no client, smaller service TTL wins", ++ leaseID: 600, ++ serviceTTL: 120, ++ minLeaseTTL: 0, ++ maxLeaseTTL: 0, ++ hasClient: false, ++ expectedTTL: 120, ++ }, ++ { ++ name: "custom bounds, no client", ++ leaseID: 0x12345678FFFFFFFF, ++ serviceTTL: 0, ++ minLeaseTTL: 60, // 1 minute ++ maxLeaseTTL: 3600, // 1 hour ++ hasClient: false, ++ expectedTTL: defaultTTL, ++ }, ++ { ++ name: "zero service TTL with lease ID", ++ leaseID: 600, ++ serviceTTL: 0, ++ minLeaseTTL: 0, ++ maxLeaseTTL: 0, ++ hasClient: false, ++ expectedTTL: defaultTTL, ++ }, ++ { ++ name: "both zero, falls back to default", ++ leaseID: 0, ++ serviceTTL: 0, ++ minLeaseTTL: 0, ++ maxLeaseTTL: 0, ++ hasClient: false, ++ expectedTTL: defaultTTL, ++ }, ++ } ++ ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ // Create Etcd instance with test configuration ++ e := &Etcd{ ++ MinLeaseTTL: tt.minLeaseTTL, ++ MaxLeaseTTL: tt.maxLeaseTTL, ++ } ++ ++ // Create test data ++ kv := &mvccpb.KeyValue{ ++ Key: []byte("/test/service"), ++ Value: []byte(`{"host": "test.example.com"}`), ++ Lease: tt.leaseID, ++ } ++ ++ serv := &msg.Service{ ++ Host: "test.example.com", ++ TTL: tt.serviceTTL, ++ } ++ ++ resultingTTL := e.TTL(kv, serv) ++ ++ if resultingTTL != tt.expectedTTL { ++ t.Errorf("TTL() = %d, expected %d", resultingTTL, tt.expectedTTL) ++ } ++ }) ++ } ++} +-- +2.43.0 + diff --git a/coredns.spec b/coredns.spec index 1a34410..ed66772 100644 --- a/coredns.spec +++ b/coredns.spec @@ -2,13 +2,13 @@ %global debug_package %{nil} Name: coredns Version: 1.11.3 -Release: 1.4 +Release: 1.5 Summary: CoreDNS is a DNS server/forwarder, written in Go License: Apache-2.0 URL: https://coredns.io Source0: https://github.com/%{name}/%{name}/archive/v%{version}.tar.gz Source1: vendor.tar.gz - +Patch1: CVE-2025-58063.patch Source1000: net_loong64.tar.gz Source1001: net_sw64.tar.gz Source1002: sys_loong64.tar.gz @@ -29,6 +29,7 @@ Help document for the coredns package. %prep %setup -q -a1 +%patch -P1 -p1 %ifarch sw_64 loongarch64 %__rm -rf vendor/golang.org/x/{sys,net} %ifarch loongarch64 @@ -86,6 +87,10 @@ install -m 0644 man/coredns-*.7 %{buildroot}/%{_mandir}/man7 %{_mandir}/man7/coredns-* %changelog +* Wed Sep 10 2025 jinshuaiyu 1.11.3-1.5 +- fix CVE-2025-58063 +- Instead of casting lease ID to uint32, fix the TTL() function to use etcd time-to-live API for determining TTL. + * Mon Nov 11 2024 Paco Xu - 1.11.3-1.4 - Bump coredns to v1.11.3 -- Gitee