diff options
| author | Archana Polampalli <archana.polampalli@windriver.com> | 2025-11-28 21:37:56 +0530 |
|---|---|---|
| committer | Steve Sakoman <steve@sakoman.com> | 2025-12-05 06:56:34 -0800 |
| commit | dd0a2c24702152c76547b1db2690ff5af3d23f06 (patch) | |
| tree | 0055e85c4556e6e554520994d0ed64852b8b4e35 | |
| parent | c5794d82b0a6d985fbec5b50133a812c2c1dbb97 (diff) | |
| download | poky-dd0a2c24702152c76547b1db2690ff5af3d23f06.tar.gz | |
go: fix CVE-2025-58187
Due to the design of the name constraint checking algorithm, the processing time
of some inputs scale non-linearly with respect to the size of the certificate.
This affects programs which validate arbitrary certificate chains.
(From OE-Core rev: cea9fcf1b21b1b35b88986b676d712ab8ffa9d67)
Signed-off-by: Archana Polampalli <archana.polampalli@windriver.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
| -rw-r--r-- | meta/recipes-devtools/go/go-1.17.13.inc | 1 | ||||
| -rw-r--r-- | meta/recipes-devtools/go/go-1.18/CVE-2025-58187.patch | 349 |
2 files changed, 350 insertions, 0 deletions
diff --git a/meta/recipes-devtools/go/go-1.17.13.inc b/meta/recipes-devtools/go/go-1.17.13.inc index 465f24e108..c5aa3f9786 100644 --- a/meta/recipes-devtools/go/go-1.17.13.inc +++ b/meta/recipes-devtools/go/go-1.17.13.inc | |||
| @@ -69,6 +69,7 @@ SRC_URI = "https://golang.org/dl/go${PV}.src.tar.gz;name=main \ | |||
| 69 | file://CVE-2025-47907.patch \ | 69 | file://CVE-2025-47907.patch \ |
| 70 | file://CVE-2025-47906.patch \ | 70 | file://CVE-2025-47906.patch \ |
| 71 | file://CVE-2024-24783.patch \ | 71 | file://CVE-2024-24783.patch \ |
| 72 | file://CVE-2025-58187.patch \ | ||
| 72 | " | 73 | " |
| 73 | SRC_URI[main.sha256sum] = "a1a48b23afb206f95e7bbaa9b898d965f90826f6f1d1fc0c1d784ada0cd300fd" | 74 | SRC_URI[main.sha256sum] = "a1a48b23afb206f95e7bbaa9b898d965f90826f6f1d1fc0c1d784ada0cd300fd" |
| 74 | 75 | ||
diff --git a/meta/recipes-devtools/go/go-1.18/CVE-2025-58187.patch b/meta/recipes-devtools/go/go-1.18/CVE-2025-58187.patch new file mode 100644 index 0000000000..810487674c --- /dev/null +++ b/meta/recipes-devtools/go/go-1.18/CVE-2025-58187.patch | |||
| @@ -0,0 +1,349 @@ | |||
| 1 | From f334417e71f8b078ad64035bddb6df7f8910da6c Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Neal Patel <nealpatel@google.com> | ||
| 3 | Date: Mon, 15 Sep 2025 16:31:22 -0400 | ||
| 4 | Subject: [PATCH] crypto/x509: improve domain name verification | ||
| 5 | |||
| 6 | Don't use domainToReverseLabels to check if domain names are valid, | ||
| 7 | since it is not particularly performant, and can contribute to DoS | ||
| 8 | vectors. Instead just iterate over the name and enforce the properties | ||
| 9 | we care about. | ||
| 10 | |||
| 11 | This also enforces that DNS names, both in SANs and name constraints, | ||
| 12 | are valid. We previously allowed invalid SANs, because some | ||
| 13 | intermediates had these weird names (see #23995), but there are | ||
| 14 | currently no trusted intermediates that have this property, and since we | ||
| 15 | target the web PKI, supporting this particular case is not a high | ||
| 16 | priority. | ||
| 17 | |||
| 18 | Thank you to Jakub Ciolek for reporting this issue. | ||
| 19 | |||
| 20 | Fixes CVE-2025-58187 | ||
| 21 | For #75681 | ||
| 22 | Fixes #75714 | ||
| 23 | |||
| 24 | Change-Id: I6ebce847dcbe5fc63ef2f9a74f53f11c4c56d3d1 | ||
| 25 | Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2820 | ||
| 26 | Reviewed-by: Damien Neil <dneil@google.com> | ||
| 27 | Reviewed-by: Roland Shoemaker <bracewell@google.com> | ||
| 28 | Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2982 | ||
| 29 | Reviewed-by: Nicholas Husin <husin@google.com> | ||
| 30 | Reviewed-on: https://go-review.googlesource.com/c/go/+/709839 | ||
| 31 | Auto-Submit: Michael Pratt <mpratt@google.com> | ||
| 32 | Reviewed-by: Carlos Amedee <carlos@golang.org> | ||
| 33 | TryBot-Bypass: Michael Pratt <mpratt@google.com> | ||
| 34 | |||
| 35 | CVE: CVE-2025-58187 | ||
| 36 | |||
| 37 | Upstream-Status: Backport [https://github.com/golang/go/commit/f334417e71f8b078ad64035bddb6df7f8910da6c] | ||
| 38 | |||
| 39 | Signed-off-by: Archana Polampalli <archana.polampalli@windriver.com> | ||
| 40 | --- | ||
| 41 | src/crypto/x509/name_constraints_test.go | 66 ++------------------ | ||
| 42 | src/crypto/x509/parser.go | 77 ++++++++++++++---------- | ||
| 43 | src/crypto/x509/parser_test.go | 43 +++++++++++++ | ||
| 44 | src/crypto/x509/verify.go | 1 + | ||
| 45 | 4 files changed, 95 insertions(+), 92 deletions(-) | ||
| 46 | |||
| 47 | diff --git a/src/crypto/x509/name_constraints_test.go b/src/crypto/x509/name_constraints_test.go | ||
| 48 | index c59a7dc..d4f7d41 100644 | ||
| 49 | --- a/src/crypto/x509/name_constraints_test.go | ||
| 50 | +++ b/src/crypto/x509/name_constraints_test.go | ||
| 51 | @@ -1452,63 +1452,7 @@ var nameConstraintsTests = []nameConstraintsTest{ | ||
| 52 | requestedEKUs: []ExtKeyUsage{ExtKeyUsageServerAuth}, | ||
| 53 | }, | ||
| 54 | |||
| 55 | - // An invalid DNS SAN should be detected only at validation time so | ||
| 56 | - // that we can process CA certificates in the wild that have invalid SANs. | ||
| 57 | - // See https://github.com/golang/go/issues/23995 | ||
| 58 | - | ||
| 59 | - // #77: an invalid DNS or mail SAN will not be detected if name constraint | ||
| 60 | - // checking is not triggered. | ||
| 61 | - { | ||
| 62 | - roots: make([]constraintsSpec, 1), | ||
| 63 | - intermediates: [][]constraintsSpec{ | ||
| 64 | - { | ||
| 65 | - {}, | ||
| 66 | - }, | ||
| 67 | - }, | ||
| 68 | - leaf: leafSpec{ | ||
| 69 | - sans: []string{"dns:this is invalid", "email:this @ is invalid"}, | ||
| 70 | - }, | ||
| 71 | - }, | ||
| 72 | - | ||
| 73 | - // #78: an invalid DNS SAN will be detected if any name constraint checking | ||
| 74 | - // is triggered. | ||
| 75 | - { | ||
| 76 | - roots: []constraintsSpec{ | ||
| 77 | - { | ||
| 78 | - bad: []string{"uri:"}, | ||
| 79 | - }, | ||
| 80 | - }, | ||
| 81 | - intermediates: [][]constraintsSpec{ | ||
| 82 | - { | ||
| 83 | - {}, | ||
| 84 | - }, | ||
| 85 | - }, | ||
| 86 | - leaf: leafSpec{ | ||
| 87 | - sans: []string{"dns:this is invalid"}, | ||
| 88 | - }, | ||
| 89 | - expectedError: "cannot parse dnsName", | ||
| 90 | - }, | ||
| 91 | - | ||
| 92 | - // #79: an invalid email SAN will be detected if any name constraint | ||
| 93 | - // checking is triggered. | ||
| 94 | - { | ||
| 95 | - roots: []constraintsSpec{ | ||
| 96 | - { | ||
| 97 | - bad: []string{"uri:"}, | ||
| 98 | - }, | ||
| 99 | - }, | ||
| 100 | - intermediates: [][]constraintsSpec{ | ||
| 101 | - { | ||
| 102 | - {}, | ||
| 103 | - }, | ||
| 104 | - }, | ||
| 105 | - leaf: leafSpec{ | ||
| 106 | - sans: []string{"email:this @ is invalid"}, | ||
| 107 | - }, | ||
| 108 | - expectedError: "cannot parse rfc822Name", | ||
| 109 | - }, | ||
| 110 | - | ||
| 111 | - // #80: if several EKUs are requested, satisfying any of them is sufficient. | ||
| 112 | + // #77: if several EKUs are requested, satisfying any of them is sufficient. | ||
| 113 | { | ||
| 114 | roots: make([]constraintsSpec, 1), | ||
| 115 | intermediates: [][]constraintsSpec{ | ||
| 116 | @@ -1523,7 +1467,7 @@ var nameConstraintsTests = []nameConstraintsTest{ | ||
| 117 | requestedEKUs: []ExtKeyUsage{ExtKeyUsageClientAuth, ExtKeyUsageEmailProtection}, | ||
| 118 | }, | ||
| 119 | |||
| 120 | - // #81: EKUs that are not asserted in VerifyOpts are not required to be | ||
| 121 | + // #78: EKUs that are not asserted in VerifyOpts are not required to be | ||
| 122 | // nested. | ||
| 123 | { | ||
| 124 | roots: make([]constraintsSpec, 1), | ||
| 125 | @@ -1542,7 +1486,7 @@ var nameConstraintsTests = []nameConstraintsTest{ | ||
| 126 | }, | ||
| 127 | }, | ||
| 128 | |||
| 129 | - // #82: a certificate without SANs and CN is accepted in a constrained chain. | ||
| 130 | + // #79: a certificate without SANs and CN is accepted in a constrained chain. | ||
| 131 | { | ||
| 132 | roots: []constraintsSpec{ | ||
| 133 | { | ||
| 134 | @@ -1559,7 +1503,7 @@ var nameConstraintsTests = []nameConstraintsTest{ | ||
| 135 | }, | ||
| 136 | }, | ||
| 137 | |||
| 138 | - // #83: a certificate without SANs and with a CN that does not parse as a | ||
| 139 | + // #80: a certificate without SANs and with a CN that does not parse as a | ||
| 140 | // hostname is accepted in a constrained chain. | ||
| 141 | { | ||
| 142 | roots: []constraintsSpec{ | ||
| 143 | @@ -1578,7 +1522,7 @@ var nameConstraintsTests = []nameConstraintsTest{ | ||
| 144 | }, | ||
| 145 | }, | ||
| 146 | |||
| 147 | - // #84: a certificate with SANs and CN is accepted in a constrained chain. | ||
| 148 | + // #81: a certificate with SANs and CN is accepted in a constrained chain. | ||
| 149 | { | ||
| 150 | roots: []constraintsSpec{ | ||
| 151 | { | ||
| 152 | diff --git a/src/crypto/x509/parser.go b/src/crypto/x509/parser.go | ||
| 153 | index 635e74b..0788210 100644 | ||
| 154 | --- a/src/crypto/x509/parser.go | ||
| 155 | +++ b/src/crypto/x509/parser.go | ||
| 156 | @@ -391,10 +391,14 @@ func parseSANExtension(der cryptobyte.String) (dnsNames, emailAddresses []string | ||
| 157 | if err := isIA5String(email); err != nil { | ||
| 158 | return errors.New("x509: SAN rfc822Name is malformed") | ||
| 159 | } | ||
| 160 | + parsed, ok := parseRFC2821Mailbox(email) | ||
| 161 | + if !ok || (ok && !domainNameValid(parsed.domain, false)) { | ||
| 162 | + return errors.New("x509: SAN rfc822Name is malformed") | ||
| 163 | + } | ||
| 164 | emailAddresses = append(emailAddresses, email) | ||
| 165 | case nameTypeDNS: | ||
| 166 | name := string(data) | ||
| 167 | - if err := isIA5String(name); err != nil { | ||
| 168 | + if err := isIA5String(name); err != nil || (err == nil && !domainNameValid(name, false)) { | ||
| 169 | return errors.New("x509: SAN dNSName is malformed") | ||
| 170 | } | ||
| 171 | dnsNames = append(dnsNames, string(name)) | ||
| 172 | @@ -404,14 +408,9 @@ func parseSANExtension(der cryptobyte.String) (dnsNames, emailAddresses []string | ||
| 173 | return errors.New("x509: SAN uniformResourceIdentifier is malformed") | ||
| 174 | } | ||
| 175 | uri, err := url.Parse(uriStr) | ||
| 176 | - if err != nil { | ||
| 177 | + if err != nil || (err == nil && uri.Host != "" && !domainNameValid(uri.Host, false)) { | ||
| 178 | return fmt.Errorf("x509: cannot parse URI %q: %s", uriStr, err) | ||
| 179 | } | ||
| 180 | - if len(uri.Host) > 0 { | ||
| 181 | - if _, ok := domainToReverseLabels(uri.Host); !ok { | ||
| 182 | - return fmt.Errorf("x509: cannot parse URI %q: invalid domain", uriStr) | ||
| 183 | - } | ||
| 184 | - } | ||
| 185 | uris = append(uris, uri) | ||
| 186 | case nameTypeIP: | ||
| 187 | switch len(data) { | ||
| 188 | @@ -551,15 +550,7 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle | ||
| 189 | return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error()) | ||
| 190 | } | ||
| 191 | |||
| 192 | - trimmedDomain := domain | ||
| 193 | - if len(trimmedDomain) > 0 && trimmedDomain[0] == '.' { | ||
| 194 | - // constraints can have a leading | ||
| 195 | - // period to exclude the domain | ||
| 196 | - // itself, but that's not valid in a | ||
| 197 | - // normal domain name. | ||
| 198 | - trimmedDomain = trimmedDomain[1:] | ||
| 199 | - } | ||
| 200 | - if _, ok := domainToReverseLabels(trimmedDomain); !ok { | ||
| 201 | + if !domainNameValid(domain, true) { | ||
| 202 | return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse dnsName constraint %q", domain) | ||
| 203 | } | ||
| 204 | dnsNames = append(dnsNames, domain) | ||
| 205 | @@ -600,12 +591,7 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle | ||
| 206 | return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint) | ||
| 207 | } | ||
| 208 | } else { | ||
| 209 | - // Otherwise it's a domain name. | ||
| 210 | - domain := constraint | ||
| 211 | - if len(domain) > 0 && domain[0] == '.' { | ||
| 212 | - domain = domain[1:] | ||
| 213 | - } | ||
| 214 | - if _, ok := domainToReverseLabels(domain); !ok { | ||
| 215 | + if !domainNameValid(constraint, true) { | ||
| 216 | return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint) | ||
| 217 | } | ||
| 218 | } | ||
| 219 | @@ -621,15 +607,7 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle | ||
| 220 | return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q: cannot be IP address", domain) | ||
| 221 | } | ||
| 222 | |||
| 223 | - trimmedDomain := domain | ||
| 224 | - if len(trimmedDomain) > 0 && trimmedDomain[0] == '.' { | ||
| 225 | - // constraints can have a leading | ||
| 226 | - // period to exclude the domain itself, | ||
| 227 | - // but that's not valid in a normal | ||
| 228 | - // domain name. | ||
| 229 | - trimmedDomain = trimmedDomain[1:] | ||
| 230 | - } | ||
| 231 | - if _, ok := domainToReverseLabels(trimmedDomain); !ok { | ||
| 232 | + if !domainNameValid(domain, true) { | ||
| 233 | return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q", domain) | ||
| 234 | } | ||
| 235 | uriDomains = append(uriDomains, domain) | ||
| 236 | @@ -1011,3 +989,40 @@ func ParseCertificates(der []byte) ([]*Certificate, error) { | ||
| 237 | } | ||
| 238 | return certs, nil | ||
| 239 | } | ||
| 240 | + | ||
| 241 | +// domainNameValid does minimal domain name validity checking. In particular it | ||
| 242 | +// enforces the following properties: | ||
| 243 | +// - names cannot have the trailing period | ||
| 244 | +// - names can only have a leading period if constraint is true | ||
| 245 | +// - names must be <= 253 characters | ||
| 246 | +// - names cannot have empty labels | ||
| 247 | +// - names cannot labels that are longer than 63 characters | ||
| 248 | +// | ||
| 249 | +// Note that this does not enforce the LDH requirements for domain names. | ||
| 250 | +func domainNameValid(s string, constraint bool) bool { | ||
| 251 | + if len(s) == 0 && constraint { | ||
| 252 | + return true | ||
| 253 | + } | ||
| 254 | + if len(s) == 0 || (!constraint && s[0] == '.') || s[len(s)-1] == '.' || len(s) > 253 { | ||
| 255 | + return false | ||
| 256 | + } | ||
| 257 | + lastDot := -1 | ||
| 258 | + if constraint && s[0] == '.' { | ||
| 259 | + s = s[1:] | ||
| 260 | + } | ||
| 261 | + | ||
| 262 | + for i := 0; i <= len(s); i++ { | ||
| 263 | + if i == len(s) || s[i] == '.' { | ||
| 264 | + labelLen := i | ||
| 265 | + if lastDot >= 0 { | ||
| 266 | + labelLen -= lastDot + 1 | ||
| 267 | + } | ||
| 268 | + if labelLen == 0 || labelLen > 63 { | ||
| 269 | + return false | ||
| 270 | + } | ||
| 271 | + lastDot = i | ||
| 272 | + } | ||
| 273 | + } | ||
| 274 | + | ||
| 275 | + return true | ||
| 276 | +} | ||
| 277 | diff --git a/src/crypto/x509/parser_test.go b/src/crypto/x509/parser_test.go | ||
| 278 | index d7cf7ea..95ed116 100644 | ||
| 279 | --- a/src/crypto/x509/parser_test.go | ||
| 280 | +++ b/src/crypto/x509/parser_test.go | ||
| 281 | @@ -5,6 +5,7 @@ package x509 | ||
| 282 | |||
| 283 | import ( | ||
| 284 | "encoding/asn1" | ||
| 285 | + "strings" | ||
| 286 | "testing" | ||
| 287 | |||
| 288 | cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" | ||
| 289 | @@ -100,3 +101,45 @@ func TestParseASN1String(t *testing.T) { | ||
| 290 | }) | ||
| 291 | } | ||
| 292 | } | ||
| 293 | + | ||
| 294 | +func TestDomainNameValid(t *testing.T) { | ||
| 295 | + for _, tc := range []struct { | ||
| 296 | + name string | ||
| 297 | + dnsName string | ||
| 298 | + constraint bool | ||
| 299 | + valid bool | ||
| 300 | + }{ | ||
| 301 | + {"empty name, name", "", false, false}, | ||
| 302 | + {"empty name, constraint", "", true, true}, | ||
| 303 | + {"empty label, name", "a..a", false, false}, | ||
| 304 | + {"empty label, constraint", "a..a", true, false}, | ||
| 305 | + {"period, name", ".", false, false}, | ||
| 306 | + {"period, constraint", ".", true, false}, // TODO(roland): not entirely clear if this is a valid constraint (require at least one label?) | ||
| 307 | + {"valid, name", "a.b.c", false, true}, | ||
| 308 | + {"valid, constraint", "a.b.c", true, true}, | ||
| 309 | + {"leading period, name", ".a.b.c", false, false}, | ||
| 310 | + {"leading period, constraint", ".a.b.c", true, true}, | ||
| 311 | + {"trailing period, name", "a.", false, false}, | ||
| 312 | + {"trailing period, constraint", "a.", true, false}, | ||
| 313 | + {"bare label, name", "a", false, true}, | ||
| 314 | + {"bare label, constraint", "a", true, true}, | ||
| 315 | + {"254 char label, name", strings.Repeat("a.a", 84) + "aaa", false, false}, | ||
| 316 | + {"254 char label, constraint", strings.Repeat("a.a", 84) + "aaa", true, false}, | ||
| 317 | + {"253 char label, name", strings.Repeat("a.a", 84) + "aa", false, false}, | ||
| 318 | + {"253 char label, constraint", strings.Repeat("a.a", 84) + "aa", true, false}, | ||
| 319 | + {"64 char single label, name", strings.Repeat("a", 64), false, false}, | ||
| 320 | + {"64 char single label, constraint", strings.Repeat("a", 64), true, false}, | ||
| 321 | + {"63 char single label, name", strings.Repeat("a", 63), false, true}, | ||
| 322 | + {"63 char single label, constraint", strings.Repeat("a", 63), true, true}, | ||
| 323 | + {"64 char label, name", "a." + strings.Repeat("a", 64), false, false}, | ||
| 324 | + {"64 char label, constraint", "a." + strings.Repeat("a", 64), true, false}, | ||
| 325 | + {"63 char label, name", "a." + strings.Repeat("a", 63), false, true}, | ||
| 326 | + {"63 char label, constraint", "a." + strings.Repeat("a", 63), true, true}, | ||
| 327 | + } { | ||
| 328 | + t.Run(tc.name, func(t *testing.T) { | ||
| 329 | + if tc.valid != domainNameValid(tc.dnsName, tc.constraint) { | ||
| 330 | + t.Errorf("domainNameValid(%q, %t) = %v; want %v", tc.dnsName, tc.constraint, !tc.valid, tc.valid) | ||
| 331 | + } | ||
| 332 | + }) | ||
| 333 | + } | ||
| 334 | +} | ||
| 335 | diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go | ||
| 336 | index 3e95808..fb2f4b2 100644 | ||
| 337 | --- a/src/crypto/x509/verify.go | ||
| 338 | +++ b/src/crypto/x509/verify.go | ||
| 339 | @@ -357,6 +357,7 @@ func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) { | ||
| 340 | // domainToReverseLabels converts a textual domain name like foo.example.com to | ||
| 341 | // the list of labels in reverse order, e.g. ["com", "example", "foo"]. | ||
| 342 | func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) { | ||
| 343 | + reverseLabels = make([]string, 0, strings.Count(domain, ".")+1) | ||
| 344 | for len(domain) > 0 { | ||
| 345 | if i := strings.LastIndexByte(domain, '.'); i == -1 { | ||
| 346 | reverseLabels = append(reverseLabels, domain) | ||
| 347 | -- | ||
| 348 | 2.40.0 | ||
| 349 | |||
