From 1a581e49448dea58ceac2b85d19a0a391ed430da Mon Sep 17 00:00:00 2001 From: huzhangying Date: Wed, 10 Dec 2025 09:38:49 +0800 Subject: [PATCH] [backport]fix CVE-2025-61729,CVE-2025-61727 (cherry picked from commit 552a6ce8608af12767ca09f630e396541453467b) --- ...5-61729-crypto-x509-prevent-Hostname.patch | 196 +++++++++++++++ ...61727-crypto-x509-excluded-subdomain.patch | 228 ++++++++++++++++++ golang.spec | 10 +- 3 files changed, 433 insertions(+), 1 deletion(-) create mode 100644 backport-0047-CVE-2025-61729-crypto-x509-prevent-Hostname.patch create mode 100644 backport-0048-CVE-2025-61727-crypto-x509-excluded-subdomain.patch diff --git a/backport-0047-CVE-2025-61729-crypto-x509-prevent-Hostname.patch b/backport-0047-CVE-2025-61729-crypto-x509-prevent-Hostname.patch new file mode 100644 index 0000000..787e13a --- /dev/null +++ b/backport-0047-CVE-2025-61729-crypto-x509-prevent-Hostname.patch @@ -0,0 +1,196 @@ +From 3a842bd5c6aa8eefa13c0174de3ab361e50bd672 Mon Sep 17 00:00:00 2001 +From: Nicholas S. Husin +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. + +Reference: https://go-review.googlesource.com/c/go/+/725820 +Conflict: fix src/crypto/x509/verify_test.go + +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 +Reviewed-by: Damien Neil +Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/3220 +Reviewed-by: Roland Shoemaker +Reviewed-on: https://go-review.googlesource.com/c/go/+/725820 +Reviewed-by: Dmitri Shuralyov +TryBot-Bypass: Dmitri Shuralyov +Auto-Submit: Dmitri Shuralyov +Reviewed-by: Mark Freeman +--- + src/crypto/x509/verify.go | 21 ++++++---- + src/crypto/x509/verify_test.go | 70 ++++++++++++++++++++++++++++++++++ + 2 files changed, 84 insertions(+), 7 deletions(-) + +diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go +index 028c4a7..b3b3acc 100644 +--- a/src/crypto/x509/verify.go ++++ b/src/crypto/x509/verify.go +@@ -100,31 +100,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 9480059..3c3445a 100644 +--- a/src/crypto/x509/verify_test.go ++++ b/src/crypto/x509/verify_test.go +@@ -10,13 +10,16 @@ import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" ++ "crypto/rsa" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/pem" + "errors" + "fmt" + "internal/testenv" ++ "log" + "math/big" ++ "net" + "reflect" + "runtime" + "sort" +@@ -87,6 +90,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, +@@ -593,6 +616,53 @@ 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{ ++ SerialNumber: big.NewInt(1), ++ NotBefore: time.Unix(currentTime, 0), ++ NotAfter: time.Unix(currentTime, 0), ++ } ++ if ip := net.ParseIP(san); ip != nil { ++ cert.IPAddresses = repeatIPSlice(ip, count) ++ } else { ++ cert.DNSNames = repeatStringSlice(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, ++ })) ++} ++ ++func repeatStringSlice(s string, count int) []string { ++ if count <= 0 { ++ return []string{} ++ } ++ result := make([]string, count) ++ for i := range result { ++ result[i] = s ++ } ++ return result ++} ++ ++func repeatIPSlice(ip net.IP, count int) []net.IP { ++ if count <= 0 { ++ return []net.IP{} ++ } ++ result := make([]net.IP,count) ++ for i:= range result { ++ result[i] = append(net.IP(nil), ip...) ++ } ++ return result ++} ++ + const gtsIntermediate = `-----BEGIN CERTIFICATE----- + MIIFljCCA36gAwIBAgINAgO8U1lrNMcY9QFQZjANBgkqhkiG9w0BAQsFADBHMQsw + CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +-- +2.43.0 + diff --git a/backport-0048-CVE-2025-61727-crypto-x509-excluded-subdomain.patch b/backport-0048-CVE-2025-61727-crypto-x509-excluded-subdomain.patch new file mode 100644 index 0000000..a9d7407 --- /dev/null +++ b/backport-0048-CVE-2025-61727-crypto-x509-excluded-subdomain.patch @@ -0,0 +1,228 @@ +From 04db77a423cac75bb82cc9a6859991ae9c016344 Mon Sep 17 00:00:00 2001 +From: Roland Shoemaker +Date: Mon, 24 Nov 2025 08:46:08 -0800 +Subject: [PATCH] [release-branch.go1.24] crypto/x509: + excluded subdomain constraints preclude wildcard SANs + +When evaluating name constraints in a certificate chain, the presence of +an excluded subdomain constraint (e.g., excluding "test.example.com") +should preclude the use of a wildcard SAN (e.g., "*.example.com"). + +Reference:https://go-review.googlesource.com/c/go/+/724401 +Conflict:no + +Fixes #76442 +Fixes #76463 +Fixes CVE-2025-61727 + +Change-Id: I42a0da010cb36d2ec9d1239ae3f61cf25eb78bba +Reviewed-on: https://go-review.googlesource.com/c/go/+/724401 +Reviewed-by: Nicholas Husin +Reviewed-by: Daniel McCarney +LUCI-TryBot-Result: Go LUCI +Reviewed-by: Nicholas Husin +Reviewed-by: Neal Patel +--- + src/crypto/x509/name_constraints_test.go | 34 ++++++++++++++++++++ + src/crypto/x509/verify.go | 40 +++++++++++++++--------- + src/crypto/x509/verify_test.go | 2 +- + 3 files changed, 60 insertions(+), 16 deletions(-) + +diff --git a/src/crypto/x509/name_constraints_test.go b/src/crypto/x509/name_constraints_test.go +index afc1409..cd326e0 100644 +--- a/src/crypto/x509/name_constraints_test.go ++++ b/src/crypto/x509/name_constraints_test.go +@@ -1625,6 +1625,40 @@ var nameConstraintsTests = []nameConstraintsTest{ + }, + expectedError: "URI with IP", + }, ++ // #87: subdomain excluded constraints preclude wildcard names ++ { ++ roots: []constraintsSpec{ ++ { ++ bad: []string{"dns:foo.example.com"}, ++ }, ++ }, ++ intermediates: [][]constraintsSpec{ ++ { ++ {}, ++ }, ++ }, ++ leaf: leafSpec{ ++ sans: []string{"dns:*.example.com"}, ++ }, ++ expectedError: "\"*.example.com\" is excluded by constraint \"foo.example.com\"", ++ }, ++ // #88: wildcard names are not matched by subdomain permitted constraints ++ { ++ roots: []constraintsSpec{ ++ { ++ ok: []string{"dns:foo.example.com"}, ++ }, ++ }, ++ intermediates: [][]constraintsSpec{ ++ { ++ {}, ++ }, ++ }, ++ leaf: leafSpec{ ++ sans: []string{"dns:*.example.com"}, ++ }, ++ expectedError: "\"*.example.com\" is not permitted", ++ }, + } + + func makeConstraintsCACert(constraints constraintsSpec, name string, key *ecdsa.PrivateKey, parent *Certificate, parentKey *ecdsa.PrivateKey) (*Certificate, error) { +diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go +index b3b3acc..71838fd 100644 +--- a/src/crypto/x509/verify.go ++++ b/src/crypto/x509/verify.go +@@ -400,7 +400,7 @@ func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) { + return reverseLabels, true + } + +-func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { ++func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string, excluded bool, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { + // If the constraint contains an @, then it specifies an exact mailbox + // name. + if strings.Contains(constraint, "@") { +@@ -413,10 +413,10 @@ func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string, reversedDom + + // Otherwise the constraint is like a DNS constraint of the domain part + // of the mailbox. +- return matchDomainConstraint(mailbox.domain, constraint, reversedDomainsCache, reversedConstraintsCache) ++ return matchDomainConstraint(mailbox.domain, constraint, excluded, reversedDomainsCache, reversedConstraintsCache) + } + +-func matchURIConstraint(uri *url.URL, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { ++func matchURIConstraint(uri *url.URL, constraint string, excluded bool, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { + // From RFC 5280, Section 4.2.1.10: + // “a uniformResourceIdentifier that does not include an authority + // component with a host name specified as a fully qualified domain +@@ -445,7 +445,7 @@ func matchURIConstraint(uri *url.URL, constraint string, reversedDomainsCache ma + return false, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", uri.String()) + } + +- return matchDomainConstraint(host, constraint, reversedDomainsCache, reversedConstraintsCache) ++ return matchDomainConstraint(host, constraint, excluded, reversedDomainsCache, reversedConstraintsCache) + } + + func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) { +@@ -462,7 +462,7 @@ func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) { + return true, nil + } + +-func matchDomainConstraint(domain, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { ++func matchDomainConstraint(domain, constraint string, excluded bool, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { + // The meaning of zero length constraints is not specified, but this + // code follows NSS and accepts them as matching everything. + if len(constraint) == 0 { +@@ -479,6 +479,11 @@ func matchDomainConstraint(domain, constraint string, reversedDomainsCache map[s + reversedDomainsCache[domain] = domainLabels + } + ++ wildcardDomain := false ++ if len(domain) > 0 && domain[0] == '*' { ++ wildcardDomain = true ++ } ++ + // RFC 5280 says that a leading period in a domain name means that at + // least one label must be prepended, but only for URI and email + // constraints, not DNS constraints. The code also supports that +@@ -505,6 +510,11 @@ func matchDomainConstraint(domain, constraint string, reversedDomainsCache map[s + return false, nil + } + ++ if excluded && wildcardDomain && len(domainLabels) > 1 && len(constraintLabels) > 0 { ++ domainLabels = domainLabels[:len(domainLabels)-1] ++ constraintLabels = constraintLabels[:len(constraintLabels)-1] ++ } ++ + for i, constraintLabel := range constraintLabels { + if !strings.EqualFold(constraintLabel, domainLabels[i]) { + return false, nil +@@ -524,7 +534,7 @@ func (c *Certificate) checkNameConstraints(count *int, + nameType string, + name string, + parsedName any, +- match func(parsedName, constraint any) (match bool, err error), ++ match func(parsedName, constraint any, excluded bool) (match bool, err error), + permitted, excluded any) error { + + excludedValue := reflect.ValueOf(excluded) +@@ -536,7 +546,7 @@ func (c *Certificate) checkNameConstraints(count *int, + + for i := 0; i < excludedValue.Len(); i++ { + constraint := excludedValue.Index(i).Interface() +- match, err := match(parsedName, constraint) ++ match, err := match(parsedName, constraint, true) + if err != nil { + return CertificateInvalidError{c, CANotAuthorizedForThisName, err.Error()} + } +@@ -558,7 +568,7 @@ func (c *Certificate) checkNameConstraints(count *int, + constraint := permittedValue.Index(i).Interface() + + var err error +- if ok, err = match(parsedName, constraint); err != nil { ++ if ok, err = match(parsedName, constraint, false); err != nil { + return CertificateInvalidError{c, CANotAuthorizedForThisName, err.Error()} + } + +@@ -650,8 +660,8 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V + } + + if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox, +- func(parsedName, constraint any) (bool, error) { +- return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string), reversedDomainsCache, reversedConstraintsCache) ++ func(parsedName, constraint any, excluded bool) (bool, error) { ++ return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string), excluded, reversedDomainsCache, reversedConstraintsCache) + }, c.PermittedEmailAddresses, c.ExcludedEmailAddresses); err != nil { + return err + } +@@ -663,8 +673,8 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V + } + + if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name, +- func(parsedName, constraint any) (bool, error) { +- return matchDomainConstraint(parsedName.(string), constraint.(string), reversedDomainsCache, reversedConstraintsCache) ++ func(parsedName, constraint any, excluded bool) (bool, error) { ++ return matchDomainConstraint(parsedName.(string), constraint.(string), excluded, reversedDomainsCache, reversedConstraintsCache) + }, c.PermittedDNSDomains, c.ExcludedDNSDomains); err != nil { + return err + } +@@ -677,8 +687,8 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V + } + + if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "URI", name, uri, +- func(parsedName, constraint any) (bool, error) { +- return matchURIConstraint(parsedName.(*url.URL), constraint.(string), reversedDomainsCache, reversedConstraintsCache) ++ func(parsedName, constraint any, excluded bool) (bool, error) { ++ return matchURIConstraint(parsedName.(*url.URL), constraint.(string), excluded, reversedDomainsCache, reversedConstraintsCache) + }, c.PermittedURIDomains, c.ExcludedURIDomains); err != nil { + return err + } +@@ -690,7 +700,7 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V + } + + if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "IP address", ip.String(), ip, +- func(parsedName, constraint any) (bool, error) { ++ func(parsedName, constraint any, _ bool) (bool, error) { + return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet)) + }, c.PermittedIPRanges, c.ExcludedIPRanges); err != nil { + return err +diff --git a/src/crypto/x509/verify_test.go b/src/crypto/x509/verify_test.go +index 3c3445a..9db3419 100644 +--- a/src/crypto/x509/verify_test.go ++++ b/src/crypto/x509/verify_test.go +@@ -1617,7 +1617,7 @@ var nameConstraintTests = []struct { + + func TestNameConstraints(t *testing.T) { + for i, test := range nameConstraintTests { +- result, err := matchDomainConstraint(test.domain, test.constraint, map[string][]string{}, map[string][]string{}) ++ result, err := matchDomainConstraint(test.domain, test.constraint, false, map[string][]string{}, map[string][]string{}) + + if err != nil && !test.expectError { + t.Errorf("unexpected error for test #%d: domain=%s, constraint=%s, err=%s", i, test.domain, test.constraint, err) +-- +2.43.0 + diff --git a/golang.spec b/golang.spec index 7a490e3..31ebaec 100644 --- a/golang.spec +++ b/golang.spec @@ -66,7 +66,7 @@ Name: golang Version: 1.21.4 -Release: 40 +Release: 41 Summary: The Go Programming Language License: BSD and Public Domain URL: https://golang.org/ @@ -166,6 +166,8 @@ Patch6043: backport-0043-CVE-2025-58187-crypto-x509-rework-fix-for-CVE-2025-581 Patch6044: backport-0044-CVE-2025-61723-encoding-pem-make-Decode-complexity-linear.patch Patch6045: backport-0045-CVE-2025-47912-net-url-enforce-stricter-parsing.patch Patch6046: backport-0046-CVE-2025-58186-net-http-add-httpcookiemaxnum.patch +Patch6047: backport-0047-CVE-2025-61729-crypto-x509-prevent-Hostname.patch +Patch6048: backport-0048-CVE-2025-61727-crypto-x509-excluded-subdomain.patch # Part 8001 ~ 8999 # Developed optimization features @@ -411,6 +413,12 @@ fi %files devel -f go-tests.list -f go-misc.list -f go-src.list %changelog +* Thu Dec 4 2025 huzhangying - 1.21.4-41 +- Type:CVE +- CVE:CVE-2025-61729,CVE-2025-61727 +- SUG:NA +- DESC:fix CVE-2025-61729,CVE-2025-61727 + * Thu Nov 20 2025 huzhangying - 1.21.4-40 - Type:CVE - CVE:CVE-2025-47912,CVE-2025-58186 -- Gitee