mirror of
https://git.yoctoproject.org/git/poky
synced 2026-01-01 13:58:04 +00:00
go: fix CVE-2025-47912
The Parse function permits values other than IPv6 addresses to be included in square brackets within the host component of a URL. RFC 3986 permits IPv6 addresses to be included within the host component, enclosed within square brackets. For example: "http://[::1]/". IPv4 addresses and hostnames must not appear within square brackets. Parse did not enforce this requirement. (From OE-Core rev: c5fc59eb87d0f92ba8596b7848d16d59773582a0) Signed-off-by: Archana Polampalli <archana.polampalli@windriver.com> Signed-off-by: Steve Sakoman <steve@sakoman.com>
This commit is contained in:
parent
b3b8ae2317
commit
18bfeb632b
|
|
@ -25,6 +25,7 @@ SRC_URI += "\
|
|||
file://CVE-2025-58187.patch \
|
||||
file://CVE-2025-58188.patch \
|
||||
file://CVE-2025-58189.patch \
|
||||
file://CVE-2025-47912.patch \
|
||||
"
|
||||
SRC_URI[main.sha256sum] = "012a7e1f37f362c0918c1dfa3334458ac2da1628c4b9cf4d9ca02db986e17d71"
|
||||
|
||||
|
|
|
|||
226
meta/recipes-devtools/go/go/CVE-2025-47912.patch
Normal file
226
meta/recipes-devtools/go/go/CVE-2025-47912.patch
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
From d6d2f7bf76718f1db05461cd912ae5e30d7b77ea Mon Sep 17 00:00:00 2001
|
||||
From: Ethan Lee <ethanalee@google.com>
|
||||
Date: Fri, 29 Aug 2025 17:35:55 +0000
|
||||
Subject: [PATCH] [release-branch.go1.24] 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.
|
||||
|
||||
Thanks to Enze Wang, Jingcheng Yang and Zehui Miao of Tsinghua
|
||||
University for reporting this issue.
|
||||
|
||||
Fixes CVE-2025-47912
|
||||
Fixes #75678
|
||||
Fixes #75712
|
||||
|
||||
Change-Id: Iaa41432bf0ee86de95a39a03adae5729e4deb46c
|
||||
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2680
|
||||
Reviewed-by: Damien Neil <dneil@google.com>
|
||||
Reviewed-by: Roland Shoemaker <bracewell@google.com>
|
||||
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2968
|
||||
Reviewed-by: Nicholas Husin <husin@google.com>
|
||||
Reviewed-on: https://go-review.googlesource.com/c/go/+/709838
|
||||
TryBot-Bypass: Michael Pratt <mpratt@google.com>
|
||||
Reviewed-by: Carlos Amedee <carlos@golang.org>
|
||||
Auto-Submit: Michael Pratt <mpratt@google.com>
|
||||
|
||||
CVE: CVE-2025-47912
|
||||
|
||||
Upstream-Status: Backport [https://github.com/golang/go/commit/d6d2f7bf76718f1db05461cd912ae5e30d7b77ea]
|
||||
|
||||
Signed-off-by: Archana Polampalli <archana.polampalli@windriver.com>
|
||||
---
|
||||
src/go/build/deps_test.go | 9 ++++++---
|
||||
src/net/url/url.go | 42 +++++++++++++++++++++++++++++----------
|
||||
src/net/url/url_test.go | 39 ++++++++++++++++++++++++++++++++++++
|
||||
3 files changed, 77 insertions(+), 13 deletions(-)
|
||||
|
||||
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
|
||||
index 7ce8d34..9f2663f 100644
|
||||
--- a/src/go/build/deps_test.go
|
||||
+++ b/src/go/build/deps_test.go
|
||||
@@ -209,7 +209,6 @@ var depsRules = `
|
||||
internal/types/errors,
|
||||
mime/quotedprintable,
|
||||
net/internal/socktest,
|
||||
- net/url,
|
||||
runtime/trace,
|
||||
text/scanner,
|
||||
text/tabwriter;
|
||||
@@ -252,6 +251,12 @@ var depsRules = `
|
||||
FMT
|
||||
< text/template/parse;
|
||||
|
||||
+ internal/bytealg, internal/itoa, math/bits, slices, strconv, unique
|
||||
+ < net/netip;
|
||||
+
|
||||
+ FMT, net/netip
|
||||
+ < net/url;
|
||||
+
|
||||
net/url, text/template/parse
|
||||
< text/template
|
||||
< internal/lazytemplate;
|
||||
@@ -367,8 +372,6 @@ var depsRules = `
|
||||
internal/godebug
|
||||
< internal/intern;
|
||||
|
||||
- internal/bytealg, internal/intern, internal/itoa, math/bits, sort, strconv
|
||||
- < net/netip;
|
||||
|
||||
# net is unavoidable when doing any networking,
|
||||
# so large dependencies must be kept out.
|
||||
diff --git a/src/net/url/url.go b/src/net/url/url.go
|
||||
index f362958..d2ae032 100644
|
||||
--- a/src/net/url/url.go
|
||||
+++ b/src/net/url/url.go
|
||||
@@ -13,6 +13,7 @@ package url
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
+ "net/netip"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -621,40 +622,61 @@ 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.
|
||||
+ 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")
|
||||
+ }
|
||||
+ 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 4aa20bb..fef236e 100644
|
||||
--- a/src/net/url/url_test.go
|
||||
+++ b/src/net/url/url_test.go
|
||||
@@ -383,6 +383,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
|
||||
@@ -707,6 +717,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) {
|
||||
@@ -1635,6 +1663,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.40.0
|
||||
Loading…
Reference in New Issue
Block a user