diff --git a/0083-CVE-2025-47912-net-url-enforce-stricter-parsing-of.patch b/0083-CVE-2025-47912-net-url-enforce-stricter-parsing-of.patch new file mode 100644 index 0000000000000000000000000000000000000000..1c4a1f92c71784b1015880dae382d4bf10be4f74 --- /dev/null +++ b/0083-CVE-2025-47912-net-url-enforce-stricter-parsing-of.patch @@ -0,0 +1,187 @@ +From f6f4e8b3ef21299db1ea3a343c3e55e91365a7fd Mon Sep 17 00:00:00 2001 +From: Ethan Lee +Date: Fri, 29 Aug 2025 17:35:55 +0000 +Subject: [PATCH] net/url: enforce stricter parsing of + bracketed IPv6 hostnames + +- Previously, url.Parse did not enforce validation of hostnames within + square brackets. +- RFC 3986 stipulates that only IPv6 hostnames can be embedded within + square brackets in a URL. +- Now, the parsing logic should strictly enforce that only IPv6 + hostnames can be resolved when in square brackets. IPv4, IPv4-mapped + addresses and other input will be rejected. +- Update url_test to add test cases that cover the above scenarios. + +Reference: https://go-review.googlesource.com/c/go/+/709857 +Conflict: src/net/url/url.go + +Thanks to Enze Wang, Jingcheng Yang and Zehui Miao of Tsinghua +University for reporting this issue. + +Fixes CVE-2025-47912 +Fixes #75678 + +Change-Id: Iaa41432bf0ee86de95a39a03adae5729e4deb46c +Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2680 +Reviewed-by: Damien Neil +Reviewed-by: Roland Shoemaker +Reviewed-on: https://go-review.googlesource.com/c/go/+/709857 +TryBot-Bypass: Michael Pratt +Reviewed-by: Carlos Amedee +Auto-Submit: Michael Pratt +--- + src/net/url/url.go | 41 +++++++++++++++++++++++++++++++---------- + src/net/url/url_test.go | 39 +++++++++++++++++++++++++++++++++++++++ + 2 files changed, 70 insertions(+), 10 deletions(-) + +diff --git a/src/net/url/url.go b/src/net/url/url.go +index 20de0f6..b0f944b 100644 +--- a/src/net/url/url.go ++++ b/src/net/url/url.go +@@ -14,6 +14,7 @@ import ( + "errors" + "fmt" + "sort" ++ "net" + "strconv" + "strings" + ) +@@ -617,40 +618,60 @@ func parseAuthority(authority string) (user *Userinfo, host string, err error) { + // parseHost parses host as an authority without user + // information. That is, as host[:port]. + func parseHost(host string) (string, error) { +- if strings.HasPrefix(host, "[") { ++ if openBracketIdx := strings.LastIndex(host, "["); openBracketIdx != -1 { + // Parse an IP-Literal in RFC 3986 and RFC 6874. + // E.g., "[fe80::1]", "[fe80::1%25en0]", "[fe80::1]:80". +- i := strings.LastIndex(host, "]") +- if i < 0 { ++ closeBracketIdx := strings.LastIndex(host, "]") ++ if closeBracketIdx < 0 { + return "", errors.New("missing ']' in host") + } +- colonPort := host[i+1:] ++ colonPort := host[closeBracketIdx+1:] + if !validOptionalPort(colonPort) { + return "", fmt.Errorf("invalid port %q after host", colonPort) + } ++ unescapedColonPort, err := unescape(colonPort, encodeHost) ++ if err != nil { ++ return "", err ++ } + ++ hostname := host[openBracketIdx+1 : closeBracketIdx] ++ var unescapedHostname string + // RFC 6874 defines that %25 (%-encoded percent) introduces + // the zone identifier, and the zone identifier can use basically + // any %-encoding it likes. That's different from the host, which + // can only %-encode non-ASCII bytes. + // We do impose some restrictions on the zone, to avoid stupidity + // like newlines. +- zone := strings.Index(host[:i], "%25") +- if zone >= 0 { +- host1, err := unescape(host[:zone], encodeHost) ++ zoneIdx := strings.Index(hostname, "%25") ++ if zoneIdx >= 0 { ++ hostPart, err := unescape(hostname[:zoneIdx], encodeHost) + if err != nil { + return "", err + } +- host2, err := unescape(host[zone:i], encodeZone) ++ zonePart, err := unescape(hostname[zoneIdx:], encodeZone) + if err != nil { + return "", err + } +- host3, err := unescape(host[i:], encodeHost) ++ unescapedHostname = hostPart + zonePart ++ } else { ++ var err error ++ unescapedHostname, err = unescape(hostname, encodeHost) + if err != nil { + return "", err + } +- return host1 + host2 + host3, nil + } ++ ++ // Per RFC 3986, only a host identified by a valid ++ // IPv6 address can be enclosed by square brackets. ++ // This excludes any IPv4 or IPv4-mapped addresses. ++ ip := net.ParseIP(unescapedHostname) ++ if ip == nil { ++ return "", fmt.Errorf("invalid host: %q", unescapedHostname) ++ } ++ if ip.To4() != nil { ++ return "", errors.New("invalid IPv6 host") ++ } ++ return "[" + unescapedHostname + "]" + unescapedColonPort, nil + } else if i := strings.LastIndex(host, ":"); i != -1 { + colonPort := host[i:] + if !validOptionalPort(colonPort) { +diff --git a/src/net/url/url_test.go b/src/net/url/url_test.go +index 63c8e69..aa99c2a 100644 +--- a/src/net/url/url_test.go ++++ b/src/net/url/url_test.go +@@ -382,6 +382,16 @@ var urltests = []URLTest{ + }, + "", + }, ++ // valid IPv6 host with port and path ++ { ++ "https://[2001:db8::1]:8443/test/path", ++ &URL{ ++ Scheme: "https", ++ Host: "[2001:db8::1]:8443", ++ Path: "/test/path", ++ }, ++ "", ++ }, + // host subcomponent; IPv6 address with zone identifier in RFC 6874 + { + "http://[fe80::1%25en0]/", // alphanum zone identifier +@@ -706,6 +716,24 @@ var parseRequestURLTests = []struct { + // RFC 6874. + {"http://[fe80::1%en0]/", false}, + {"http://[fe80::1%en0]:8080/", false}, ++ ++ // Tests exercising RFC 3986 compliance ++ {"https://[1:2:3:4:5:6:7:8]", true}, // full IPv6 address ++ {"https://[2001:db8::a:b:c:d]", true}, // compressed IPv6 address ++ {"https://[fe80::1%25eth0]", true}, // link-local address with zone ID (interface name) ++ {"https://[fe80::abc:def%254]", true}, // link-local address with zone ID (interface index) ++ {"https://[2001:db8::1]/path", true}, // compressed IPv6 address with path ++ {"https://[fe80::1%25eth0]/path?query=1", true}, // link-local with zone, path, and query ++ ++ {"https://[::ffff:192.0.2.1]", false}, ++ {"https://[:1] ", false}, ++ {"https://[1:2:3:4:5:6:7:8:9]", false}, ++ {"https://[1::1::1]", false}, ++ {"https://[1:2:3:]", false}, ++ {"https://[ffff::127.0.0.4000]", false}, ++ {"https://[0:0::test.com]:80", false}, ++ {"https://[2001:db8::test.com]", false}, ++ {"https://[test.com]", false}, + } + + func TestParseRequestURI(t *testing.T) { +@@ -1631,6 +1659,17 @@ func TestParseErrors(t *testing.T) { + {"cache_object:foo", true}, + {"cache_object:foo/bar", true}, + {"cache_object/:foo/bar", false}, ++ ++ {"http://[192.168.0.1]/", true}, // IPv4 in brackets ++ {"http://[192.168.0.1]:8080/", true}, // IPv4 in brackets with port ++ {"http://[::ffff:192.168.0.1]/", true}, // IPv4-mapped IPv6 in brackets ++ {"http://[::ffff:192.168.0.1]:8080/", true}, // IPv4-mapped IPv6 in brackets with port ++ {"http://[::ffff:c0a8:1]/", true}, // IPv4-mapped IPv6 in brackets (hex) ++ {"http://[not-an-ip]/", true}, // invalid IP string in brackets ++ {"http://[fe80::1%foo]/", true}, // invalid zone format in brackets ++ {"http://[fe80::1", true}, // missing closing bracket ++ {"http://fe80::1]/", true}, // missing opening bracket ++ {"http://[test.com]/", true}, // domain name in brackets + } + for _, tt := range tests { + u, err := Parse(tt.in) +-- +2.43.0 + diff --git a/golang.spec b/golang.spec index 86ba593e39ba23e6172acf7b7e14e793750277db..1ad904edcb369953d1e2977b2fbbeec53f578ce5 100644 --- a/golang.spec +++ b/golang.spec @@ -63,7 +63,7 @@ Name: golang Version: 1.17.3 -Release: 43 +Release: 44 Summary: The Go Programming Language License: BSD and Public Domain URL: https://golang.org/ @@ -232,6 +232,7 @@ Patch6079: 0079-CVE-2025-58183-archive-tar-set-a-limit-on-the.patch Patch6080: 0080-CVE-2025-58189-crypto-tls-quote-protocols-incrypto.patch Patch6081: 0081-CVE-2025-61724-net-textproto-avoid-quadratic.patch Patch6082: 0082-CVE-2025-58185-encoding-asn1-prevent-memory-exhaustion.patch +Patch6083: 0083-CVE-2025-47912-net-url-enforce-stricter-parsing-of.patch ExclusiveArch: %{golang_arches} @@ -470,6 +471,12 @@ fi %files devel -f go-tests.list -f go-misc.list -f go-src.list %changelog +* Tue Nov 18 2025 huzhangying - 1.17.3-44 +- Type:CVE +- CVE:CVE-2025-47912 +- SUG:NA +- DESC:fix CVE-2025-47912 + * Sat Nov 8 2025 huzhangying - 1.17.3-43 - Type:CVE - CVE:CVE-2025-58183,CVE-2025-58185,CVE-2025-58189,CVE-2025-61724