diff options
| author | Roland Shoemaker <roland@golang.org> | 2025-10-08 17:13:12 -0700 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2025-10-17 15:02:30 -0700 |
| commit | a216ddc295c3e983f78a7ff911ae20ca147b09b7 (patch) | |
| tree | db602e63ef163273766795f3023b8f6342864750 | |
| parent | 8e10ef451a1b6a1e8861ced1154e1c3265bfa01b (diff) | |
| download | go-a216ddc295c3e983f78a7ff911ae20ca147b09b7.tar.xz | |
[release-branch.go1.24] 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.
Updates #75815
Fixes #75831
Change-Id: I3192f2275ad5c386f5c15006a6716bdb5282919d
Reviewed-on: https://go-review.googlesource.com/c/go/+/710375
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Ethan Lee <ethanalee@google.com>
Auto-Submit: Roland Shoemaker <roland@golang.org>
(cherry picked from commit 9db7e30bb42eed9912f5e7e9e3959f3b38879d5b)
Reviewed-on: https://go-review.googlesource.com/c/go/+/712142
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
| -rw-r--r-- | src/net/url/url.go | 6 | ||||
| -rw-r--r-- | src/net/url/url_test.go | 23 |
2 files changed, 15 insertions, 14 deletions
diff --git a/src/net/url/url.go b/src/net/url/url.go index c686239a71..1d9c1cd99f 100644 --- a/src/net/url/url.go +++ b/src/net/url/url.go @@ -670,13 +670,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) |
