VYPR
High severity7.1OSV Advisory· Published Sep 9, 2025· Updated Apr 15, 2026

CVE-2025-58063

CVE-2025-58063

Description

CoreDNS is a DNS server that chains plugins. Starting in version 1.2.0 and prior to version 1.12.4, the CoreDNS etcd plugin contains a TTL confusion vulnerability where lease IDs are incorrectly used as TTL values, enabling DNS cache pinning attacks. This effectively creates a DoS condition for DNS resolution of affected services. The TTL() function in plugin/etcd/etcd.go incorrectly casts etcd lease IDs (64-bit integers) to uint32 and uses them as TTL values. Large lease IDs become very large TTLs when cast to uint32. This enables cache pinning attacks. Version 1.12.4 contains a fix for the issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/coredns/corednsGo
>= 1.2.0, < 1.12.41.12.4

Affected products

1

Patches

2
f32329577fd5

build(deps): bump github/codeql-action from 3.30.0 to 3.30.1 (#7528)

https://github.com/coredns/corednsdependabot[bot]Sep 8, 2025via osv
3 files changed · +5 5
  • .github/workflows/codeql-analysis.yml+3 3 modified
    @@ -30,12 +30,12 @@ jobs:
             uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8  # v5.0.0
     
           - name: Initialize CodeQL
    -        uses: github/codeql-action/init@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d  # v3.30.0
    +        uses: github/codeql-action/init@f1f6e5f6af878fb37288ce1c627459e94dbf7d01  # v3.30.1
             with:
               languages: ${{ matrix.language }}
     
           - name: Autobuild
    -        uses: github/codeql-action/autobuild@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d  # v3.30.0
    +        uses: github/codeql-action/autobuild@f1f6e5f6af878fb37288ce1c627459e94dbf7d01  # v3.30.1
     
           - name: Perform CodeQL Analysis
    -        uses: github/codeql-action/analyze@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d  # v3.30.0
    +        uses: github/codeql-action/analyze@f1f6e5f6af878fb37288ce1c627459e94dbf7d01  # v3.30.1
    
  • .github/workflows/scorecards.yml+1 1 modified
    @@ -51,6 +51,6 @@ jobs:
     
           # Upload the results to GitHub's code scanning dashboard.
           - name: "Upload to code-scanning"
    -        uses: github/codeql-action/upload-sarif@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d  # v3.30.0
    +        uses: github/codeql-action/upload-sarif@f1f6e5f6af878fb37288ce1c627459e94dbf7d01  # v3.30.1
             with:
               sarif_file: results.sarif
    
  • .github/workflows/trivy-scan.yaml+1 1 modified
    @@ -28,6 +28,6 @@ jobs:
               output: 'trivy-results.sarif'
     
           - name: Upload Trivy scan results to GitHub Security tab
    -        uses: github/codeql-action/upload-sarif@2d92b76c45b91eb80fc44c74ce3fce0ee94e8f9d  # v3.30.0
    +        uses: github/codeql-action/upload-sarif@f1f6e5f6af878fb37288ce1c627459e94dbf7d01  # v3.30.1
             with:
               sarif_file: 'trivy-results.sarif'
    
e1768a5d272e

Merge commit from fork

https://github.com/coredns/corednsVille VesilehtoSep 5, 2025via ghsa
6 files changed · +333 15
  • man/coredns-etcd.7+10 2 modified
    @@ -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
    
  • plugin/etcd/etcd.go+45 12 modified
    @@ -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
    
  • plugin/etcd/README.md+4 0 modified
    @@ -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
    
  • plugin/etcd/setup.go+59 1 modified
    @@ -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
    +}
    
  • plugin/etcd/setup_test.go+103 0 modified
    @@ -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)
    +				}
    +			}
    +		})
    +	}
    +}
    
  • plugin/etcd/ttl_test.go+112 0 added
    @@ -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)
    +			}
    +		})
    +	}
    +}
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.