From b0eaf6c75b2ea11638d7cf78fd995f2d787bb4f9 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Fri, 19 Jun 2026 14:50:01 -0700 Subject: [PATCH] fix(hostmacher): patch incorrect private list (#38170) (#38173) Backport #38170 by @TheFox0x7 regression from #38039 Co-authored-by: TheFox0x7 --- modules/hostmatcher/hostmatcher.go | 21 +++++++++++---------- modules/hostmatcher/hostmatcher_test.go | 4 +++- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/modules/hostmatcher/hostmatcher.go b/modules/hostmatcher/hostmatcher.go index ae1ab102d8..0fee90a57a 100644 --- a/modules/hostmatcher/hostmatcher.go +++ b/modules/hostmatcher/hostmatcher.go @@ -65,12 +65,9 @@ var reservedIPNets = sync.OnceValue(func() []*net.IPNet { return nets }) -// isPrivateIP reports whether ip falls in a private (net.IP.IsPrivate) or reserved special-purpose +// isReservedIP reports whether ip falls in reserved special-purpose // range (see reservedIPNets) that must not be considered a public/external destination. -func isPrivateIP(ip net.IP) bool { - if ip.IsPrivate() { - return true - } +func isReservedIP(ip net.IP) bool { for _, ipNet := range reservedIPNets() { if ipNet.Contains(ip) { return true @@ -153,18 +150,22 @@ func (hl *HostMatchList) checkPattern(host string) bool { return false } -func (hl *HostMatchList) checkIP(ip net.IP) bool { +// matchesIP determines if the given IP matches any of the configured rules +func (hl *HostMatchList) matchesIP(ip net.IP) bool { if slices.Contains(hl.patterns, "*") { return true } for _, builtin := range hl.builtins { switch builtin { case MatchBuiltinExternal: - if ip.IsGlobalUnicast() && !isPrivateIP(ip) { + // External address must be a global unicast, must not be in reserved range and must not be in private range + if ip.IsGlobalUnicast() && !isReservedIP(ip) && !ip.IsPrivate() { return true } case MatchBuiltinPrivate: - if isPrivateIP(ip) { + // Private address must be global unicast, must not be in range we explicitly exclude for security reasons + // and must be in private range + if ip.IsGlobalUnicast() && !isReservedIP(ip) && ip.IsPrivate() { return true } case MatchBuiltinLoopback: @@ -195,7 +196,7 @@ func (hl *HostMatchList) MatchHostName(host string) bool { return true } if ip := net.ParseIP(hostname); ip != nil { - return hl.checkIP(ip) + return hl.matchesIP(ip) } return false } @@ -206,7 +207,7 @@ func (hl *HostMatchList) MatchIPAddr(ip net.IP) bool { return false } host := ip.String() // nil-safe, we will get "" if ip is nil - return hl.checkPattern(host) || hl.checkIP(ip) + return hl.checkPattern(host) || hl.matchesIP(ip) } // MatchHostOrIP checks if the host or IP matches an allow/deny(block) list diff --git a/modules/hostmatcher/hostmatcher_test.go b/modules/hostmatcher/hostmatcher_test.go index 61582f28d3..464354ff41 100644 --- a/modules/hostmatcher/hostmatcher_test.go +++ b/modules/hostmatcher/hostmatcher_test.go @@ -202,15 +202,17 @@ func TestReservedRanges(t *testing.T) { "198.18.0.1", // benchmarking "198.51.100.1", // TEST-NET-2 "203.0.113.1", // TEST-NET-3 + "169.254.169.254", // Cloud metadata "192.88.99.1", // 6to4 relay anycast "64:ff9b::1", // NAT64 "64:ff9b::a9fe:a9fe", // NAT64 embedding 169.254.169.254 "2001::1", // Teredo "2002::1", // 6to4 "2001:db8::1", // documentation + "fe80::1", // link local address } { addr := net.ParseIP(ip) assert.Falsef(t, external.MatchIPAddr(addr), "reserved ip %s must not be external", ip) - assert.Truef(t, private.MatchIPAddr(addr), "reserved ip %s should match private block-list", ip) + assert.Falsef(t, private.MatchIPAddr(addr), "reserved ip %s should match private block-list", ip) } }