From 0c28789bd7dfc55099cac86a3212dda0d6c091f6 Mon Sep 17 00:00:00 2001 From: Sean Liao Date: Sat, 18 Oct 2025 10:31:12 +0100 Subject: [PATCH] net/url: disallow raw IPv6 addresses in host RFC 3986 requires square brackets around IPv6 addresses. Parse's acceptance of raw IPv6 addresses is non compliant, and complicates splitting out a port. This is a resubmission of CL 710176 after the revert in CL 711800, this time with a new urlstrictipv6 godebug to control the behavior. Fixes #31024 Fixes #75223 Change-Id: I4cbe5bb84266b3efe9c98cf4300421ddf1df7291 Reviewed-on: https://go-review.googlesource.com/c/go/+/712840 Reviewed-by: Junyang Shao Reviewed-by: Damien Neil LUCI-TryBot-Result: Go LUCI --- doc/godebug.md | 5 +++ doc/next/6-stdlib/99-minor/net/url/31024.md | 4 ++ src/internal/godebugs/table.go | 1 + src/net/url/url.go | 9 +++- src/net/url/url_test.go | 46 ++++++++++++--------- src/runtime/metrics/doc.go | 5 +++ 6 files changed, 49 insertions(+), 21 deletions(-) create mode 100644 doc/next/6-stdlib/99-minor/net/url/31024.md diff --git a/doc/godebug.md b/doc/godebug.md index c12ce5311d..d9ae462b98 100644 --- a/doc/godebug.md +++ b/doc/godebug.md @@ -163,6 +163,11 @@ will fail early. The default value is `httpcookiemaxnum=3000`. Setting number of cookies. To avoid denial of service attacks, this setting and default was backported to Go 1.25.2 and Go 1.24.8. +Go 1.26 added a new `urlstrictcolons` setting that controls whether `net/url.Parse` +allows malformed hostnames containing colons outside of a bracketed IPv6 address. +The default `urlstrictcolons=1` rejects URLs such as `http://localhost:1:2` or `http://::1/`. +Colons are permitted as part of a bracketed IPv6 address, such as `http://[::1]/`. + ### Go 1.25 Go 1.25 added a new `decoratemappings` setting that controls whether the Go diff --git a/doc/next/6-stdlib/99-minor/net/url/31024.md b/doc/next/6-stdlib/99-minor/net/url/31024.md new file mode 100644 index 0000000000..11ed31e87c --- /dev/null +++ b/doc/next/6-stdlib/99-minor/net/url/31024.md @@ -0,0 +1,4 @@ +[Parse] now rejects malformed URLs containing colons in the host subcomponent, +such as `http://::1/` or `http://localhost:80:80/`. +URLs containing bracketed IPv6 addresses, such as `http://[::1]/` are still accepted. +The new GODEBUG=urlstrictcolons=0 setting restores the old behavior. diff --git a/src/internal/godebugs/table.go b/src/internal/godebugs/table.go index 271c58648d..4939e6ff10 100644 --- a/src/internal/godebugs/table.go +++ b/src/internal/godebugs/table.go @@ -67,6 +67,7 @@ var All = []Info{ {Name: "tlssha1", Package: "crypto/tls", Changed: 25, Old: "1"}, {Name: "tlsunsafeekm", Package: "crypto/tls", Changed: 22, Old: "1"}, {Name: "updatemaxprocs", Package: "runtime", Changed: 25, Old: "0"}, + {Name: "urlstrictcolons", Package: "net/url", Changed: 26, Old: "0"}, {Name: "winreadlinkvolume", Package: "os", Changed: 23, Old: "0"}, {Name: "winsymlink", Package: "os", Changed: 23, Old: "0"}, {Name: "x509keypairleaf", Package: "crypto/tls", Changed: 23, Old: "0"}, diff --git a/src/net/url/url.go b/src/net/url/url.go index 6d1d505343..ca5ff9e3d7 100644 --- a/src/net/url/url.go +++ b/src/net/url/url.go @@ -18,6 +18,7 @@ package url import ( "errors" "fmt" + "internal/godebug" "net/netip" "path" "slices" @@ -26,6 +27,8 @@ import ( _ "unsafe" // for linkname ) +var urlstrictcolons = godebug.New("urlstrictcolons") + // Error reports an error and the operation and URL that caused it. type Error struct { Op string @@ -599,7 +602,11 @@ func parseHost(host string) (string, error) { return "", errors.New("invalid IP-literal") } return "[" + unescapedHostname + "]" + unescapedColonPort, nil - } else if i := strings.LastIndex(host, ":"); i != -1 { + } else if i := strings.Index(host, ":"); i != -1 { + if j := strings.LastIndex(host, ":"); urlstrictcolons.Value() == "0" && j != i { + urlstrictcolons.IncNonDefault() + i = j + } colonPort := host[i:] if !validOptionalPort(colonPort) { return "", fmt.Errorf("invalid port %q after host", colonPort) diff --git a/src/net/url/url_test.go b/src/net/url/url_test.go index 501558403a..b601849ce5 100644 --- a/src/net/url/url_test.go +++ b/src/net/url/url_test.go @@ -13,6 +13,7 @@ import ( "io" "net" "reflect" + "strconv" "strings" "testing" ) @@ -506,26 +507,6 @@ var urltests = []URLTest{ }, "", }, - { - // Malformed IPv6 but still accepted. - "http://2b01:e34:ef40:7730:8e70:5aff:fefe:edac:8080/foo", - &URL{ - Scheme: "http", - Host: "2b01:e34:ef40:7730:8e70:5aff:fefe:edac:8080", - Path: "/foo", - }, - "", - }, - { - // Malformed IPv6 but still accepted. - "http://2b01:e34:ef40:7730:8e70:5aff:fefe:edac:/foo", - &URL{ - Scheme: "http", - Host: "2b01:e34:ef40:7730:8e70:5aff:fefe:edac:", - Path: "/foo", - }, - "", - }, { "http://[2b01:e34:ef40:7730:8e70:5aff:fefe:edac]:8080/foo", &URL{ @@ -735,6 +716,9 @@ var parseRequestURLTests = []struct { {"https://[0:0::test.com]:80", false}, {"https://[2001:db8::test.com]", false}, {"https://[test.com]", false}, + {"https://1:2:3:4:5:6:7:8", false}, + {"https://1:2:3:4:5:6:7:8:80", false}, + {"https://example.com:80:", false}, } func TestParseRequestURI(t *testing.T) { @@ -2280,3 +2264,25 @@ func TestJoinPath(t *testing.T) { } } } + +func TestParseStrictIpv6(t *testing.T) { + t.Setenv("GODEBUG", "urlstrictcolons=0") + + tests := []struct { + url string + }{ + // Malformed URLs that used to parse. + {"https://1:2:3:4:5:6:7:8"}, + {"https://1:2:3:4:5:6:7:8:80"}, + {"https://example.com:80:"}, + } + for i, tc := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + _, err := Parse(tc.url) + if err != nil { + t.Errorf("Parse(%q) error = %v, want nil", tc.url, err) + } + }) + } + +} diff --git a/src/runtime/metrics/doc.go b/src/runtime/metrics/doc.go index 05646132ce..8f908f5b52 100644 --- a/src/runtime/metrics/doc.go +++ b/src/runtime/metrics/doc.go @@ -399,6 +399,11 @@ Below is the full list of supported metrics, ordered lexicographically. The number of non-default behaviors executed by the runtime package due to a non-default GODEBUG=updatemaxprocs=... setting. + /godebug/non-default-behavior/urlstrictcolons:events + The number of non-default behaviors executed by the net/url + package due to a non-default GODEBUG=urlstrictcolons=... + setting. + /godebug/non-default-behavior/winreadlinkvolume:events The number of non-default behaviors executed by the os package due to a non-default GODEBUG=winreadlinkvolume=... setting. -- 2.52.0