]> Cypherpunks repositories - gostls13.git/commitdiff
[release-branch.go1.25] net/url: allow IP-literals with IPv4-mapped IPv6 addresses
authorRoland Shoemaker <roland@golang.org>
Thu, 9 Oct 2025 00:13:12 +0000 (17:13 -0700)
committerGopher Robot <gobot@golang.org>
Fri, 17 Oct 2025 21:59:54 +0000 (14:59 -0700)
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 #75832

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/+/712240
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
TryBot-Bypass: Dmitri Shuralyov <dmitshur@google.com>

src/net/url/url.go
src/net/url/url_test.go

index 40faa7cb9e1c6f59cae9d14be07beb1bf2cb4905..1c50e069613a812adffbe9cc97f20bdecf3d9cf8 100644 (file)
@@ -673,13 +673,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 {
index 32065583f27dd7fa4a1a1acedd781f1e6f05dcdf..6084facacc0519b0caebd232b67d0f8edbf43898 100644 (file)
@@ -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)