From: Roland Shoemaker Date: Thu, 9 Oct 2025 00:13:12 +0000 (-0700) Subject: net/url: allow IP-literals with IPv4-mapped IPv6 addresses X-Git-Tag: go1.26rc1~652 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=9db7e30bb4;p=gostls13.git net/url: allow IP-literals with IPv4-mapped IPv6 addresses The security fix we applied in CL709857 was overly broad. It applied rules from RFC 2732, which disallowed IPv4-mapped IPv6 addresses, but these were later allowed in RFC 3986, which is the canonical URI syntax RFC. Revert the portion of CL709857 which restricted IPv4-mapped addresses, and update the related tests. Fixes #75815 Change-Id: I3192f2275ad5c386f5c15006a6716bdb5282919d Reviewed-on: https://go-review.googlesource.com/c/go/+/710375 LUCI-TryBot-Result: Go LUCI Reviewed-by: Ethan Lee Auto-Submit: Roland Shoemaker --- diff --git a/src/net/url/url.go b/src/net/url/url.go index 292bc6bb12..6afa30f162 100644 --- a/src/net/url/url.go +++ b/src/net/url/url.go @@ -689,13 +689,13 @@ func parseHost(host string) (string, error) { // 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. + // This excludes any IPv4, but notably not IPv4-mapped addresses. addr, err := netip.ParseAddr(unescapedHostname) if err != nil { return "", fmt.Errorf("invalid host: %w", err) } - if addr.Is4() || addr.Is4In6() { - return "", errors.New("invalid IPv6 host") + if addr.Is4() { + return "", errors.New("invalid IP-literal") } return "[" + unescapedHostname + "]" + unescapedColonPort, nil } else if i := strings.LastIndex(host, ":"); i != -1 { diff --git a/src/net/url/url_test.go b/src/net/url/url_test.go index 32065583f2..6084facacc 100644 --- a/src/net/url/url_test.go +++ b/src/net/url/url_test.go @@ -726,7 +726,7 @@ var parseRequestURLTests = []struct { {"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://[::ffff:192.0.2.1]", true}, {"https://[:1] ", false}, {"https://[1:2:3:4:5:6:7:8:9]", false}, {"https://[1::1::1]", false}, @@ -1672,16 +1672,17 @@ func TestParseErrors(t *testing.T) { {"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 + {"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]/", false}, // IPv4-mapped IPv6 in brackets + {"http://[::ffff:192.168.0.1000]/", true}, // Out of range IPv4-mapped IPv6 in brackets + {"http://[::ffff:192.168.0.1]:8080/", false}, // IPv4-mapped IPv6 in brackets with port + {"http://[::ffff:c0a8:1]/", false}, // 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)