Files
tailscale/ipn/ipnlocal/via.go
Mike Jensen 90ed0bcf4b wgengine/netstack: block 4via6 forwards to host-scoped targets (#20866)
The packet filter only sees a via address's outer ULA, so netstack unconditionally dialed whatever IPv4 was embedded in it.

This change refuses TCP, UDP, and ping relays to host-scoped destinations after UnmapVia.

Credit to the Anthropic infrastructure security team for finding and reporting.

Fixes https://github.com/tailscale/corp/issues/46646

Change-Id: I93d8e27a2eddbce7eb3f8c5fa4677f8de3a8ed9e

Signed-off-by: Mike Jensen <mikej@tailscale.com>
2026-08-19 09:16:08 -06:00

33 lines
1.0 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package ipnlocal
import (
"net/netip"
"tailscale.com/net/tsaddr"
)
// v4BroadcastAddr is 255.255.255.255, the limited broadcast address.
var v4BroadcastAddr = netip.AddrFrom4([4]byte{255, 255, 255, 255})
// viaTargetAllowed reports whether ip may be forwarded to after unmapping a
// 4via6 destination. The packet filter only sees the outer via address, so
// this is the sole check on the embedded IPv4 target.
func viaTargetAllowed(ip netip.Addr) bool {
if !ip.Is4() {
return false // UnmapVia only returns IPv4
} else if ip.IsLoopback() || ip.IsMulticast() || ip.IsUnspecified() || ip == v4BroadcastAddr {
return false
} else if tsaddr.IsTailscaleIP(ip) {
// A CGNAT-range target may route to tailscale0 or to a site LAN
// depending on the tailnet and OS (see shouldUseOneCGNATRoute);
// it's hard to detect when forwarding would be OK, so deny always
return false
} else if ip.IsLinkLocalUnicast() {
return false
}
return true
}