1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
|
From 3a842bd5c6aa8eefa13c0174de3ab361e50bd672 Mon Sep 17 00:00:00 2001
From: "Nicholas S. Husin" <nsh@golang.org>
Date: Mon, 24 Nov 2025 14:56:23 -0500
Subject: [PATCH] [release-branch.go1.24] crypto/x509: prevent
HostnameError.Error() from consuming excessive resource
Constructing HostnameError.Error() takes O(N^2) runtime due to using a
string concatenation in a loop. Additionally, there is no limit on how
many names are included in the error message. As a result, a malicious
attacker could craft a certificate with an infinite amount of names to
unfairly consume resource.
To remediate this, we will now use strings.Builder to construct the
error message, preventing O(N^2) runtime. When a certificate has 100 or
more names, we will also not print each name individually.
Thanks to Philippe Antoine (Catena cyber) for reporting this issue.
Updates #76445
Fixes #76460
Fixes CVE-2025-61729
Change-Id: I6343776ec3289577abc76dad71766c491c1a7c81
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/3000
Reviewed-by: Neal Patel <nealpatel@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/3220
Reviewed-by: Roland Shoemaker <bracewell@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/725820
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
TryBot-Bypass: Dmitri Shuralyov <dmitshur@golang.org>
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Mark Freeman <markfreeman@google.com>
Upstream-Status: Backport [https://github.com/golang/go/commit/3a842bd5c6aa8eefa13c0174de3ab361e50bd672]
CVE: CVE-2025-61729
Signed-off-by: Vijay Anusuri <vanusuri@mvista.com>
---
src/crypto/x509/verify.go | 21 ++++++++++-----
src/crypto/x509/verify_test.go | 47 ++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+), 7 deletions(-)
diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go
index 88260ee..c167191 100644
--- a/src/crypto/x509/verify.go
+++ b/src/crypto/x509/verify.go
@@ -97,31 +97,38 @@ type HostnameError struct {
func (h HostnameError) Error() string {
c := h.Certificate
+ maxNamesIncluded := 100
if !c.hasSANExtension() && matchHostnames(c.Subject.CommonName, h.Host) {
return "x509: certificate relies on legacy Common Name field, use SANs instead"
}
- var valid string
+ var valid strings.Builder
if ip := net.ParseIP(h.Host); ip != nil {
// Trying to validate an IP
if len(c.IPAddresses) == 0 {
return "x509: cannot validate certificate for " + h.Host + " because it doesn't contain any IP SANs"
}
+ if len(c.IPAddresses) >= maxNamesIncluded {
+ return fmt.Sprintf("x509: certificate is valid for %d IP SANs, but none matched %s", len(c.IPAddresses), h.Host)
+ }
for _, san := range c.IPAddresses {
- if len(valid) > 0 {
- valid += ", "
+ if valid.Len() > 0 {
+ valid.WriteString(", ")
}
- valid += san.String()
+ valid.WriteString(san.String())
}
} else {
- valid = strings.Join(c.DNSNames, ", ")
+ if len(c.DNSNames) >= maxNamesIncluded {
+ return fmt.Sprintf("x509: certificate is valid for %d names, but none matched %s", len(c.DNSNames), h.Host)
+ }
+ valid.WriteString(strings.Join(c.DNSNames, ", "))
}
- if len(valid) == 0 {
+ if valid.Len() == 0 {
return "x509: certificate is not valid for any names, but wanted to match " + h.Host
}
- return "x509: certificate is valid for " + valid + ", not " + h.Host
+ return "x509: certificate is valid for " + valid.String() + ", not " + h.Host
}
// UnknownAuthorityError results when the certificate issuer is unknown
diff --git a/src/crypto/x509/verify_test.go b/src/crypto/x509/verify_test.go
index 5f7c834..c2c2025 100644
--- a/src/crypto/x509/verify_test.go
+++ b/src/crypto/x509/verify_test.go
@@ -9,11 +9,14 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
+ "crypto/rsa"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
+ "log"
"math/big"
+ "net"
"runtime"
"strings"
"testing"
@@ -70,6 +73,26 @@ var verifyTests = []verifyTest{
errorCallback: expectHostnameError("certificate is valid for"),
},
+ {
+ name: "TooManyDNS",
+ leaf: generatePEMCertWithRepeatSAN(1677615892, 200, "fake.dns"),
+ roots: []string{generatePEMCertWithRepeatSAN(1677615892, 200, "fake.dns")},
+ currentTime: 1677615892,
+ dnsName: "www.example.com",
+ systemSkip: true, // does not chain to a system root
+
+ errorCallback: expectHostnameError("certificate is valid for 200 names, but none matched"),
+ },
+ {
+ name: "TooManyIPs",
+ leaf: generatePEMCertWithRepeatSAN(1677615892, 150, "4.3.2.1"),
+ roots: []string{generatePEMCertWithRepeatSAN(1677615892, 150, "4.3.2.1")},
+ currentTime: 1677615892,
+ dnsName: "1.2.3.4",
+ systemSkip: true, // does not chain to a system root
+
+ errorCallback: expectHostnameError("certificate is valid for 150 IP SANs, but none matched"),
+ },
{
name: "IPMissing",
leaf: googleLeaf,
@@ -584,6 +607,30 @@ func nameToKey(name *pkix.Name) string {
return strings.Join(name.Country, ",") + "/" + strings.Join(name.Organization, ",") + "/" + strings.Join(name.OrganizationalUnit, ",") + "/" + name.CommonName
}
+func generatePEMCertWithRepeatSAN(currentTime int64, count int, san string) string {
+ cert := Certificate{
+ NotBefore: time.Unix(currentTime, 0),
+ NotAfter: time.Unix(currentTime, 0),
+ }
+ if ip := net.ParseIP(san); ip != nil {
+ cert.IPAddresses = slices.Repeat([]net.IP{ip}, count)
+ } else {
+ cert.DNSNames = slices.Repeat([]string{san}, count)
+ }
+ privKey, err := rsa.GenerateKey(rand.Reader, 4096)
+ if err != nil {
+ log.Fatal(err)
+ }
+ certBytes, err := CreateCertificate(rand.Reader, &cert, &cert, &privKey.PublicKey, privKey)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return string(pem.EncodeToMemory(&pem.Block{
+ Type: "CERTIFICATE",
+ Bytes: certBytes,
+ }))
+}
+
const geoTrustRoot = `-----BEGIN CERTIFICATE-----
MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
--
2.25.1
|