wgengine/netstack: reject unserved ports on Service (VIP) IPs (#20363)
A connection to a Tailscale Service IP on a port the service does not serve was forwarded to the underlying host. `acceptTCP` fell through to the isTailscaleIP case (a VIP is in the Tailscale IP range), which rewrote the dial target to 127.0.0.1:<port> and forwardTCP'd the connection onto whatever unrelated listener happened to be on the host's loopback at that port. This is reachable through the service IP by any peer which was granted access only to the service (dst: svc:foo), so it exposes host ports the peer has no ACL access to via the machine's regular IP. This happens when there tailscaled has a Tun interface and the forward bits are set. In this commit, we added a guard in acceptTCP, before the isTailscaleIP case that RSTs connections to a VIP service IP on a port with no serve handler. Served ports return earlier via TCPHandlerForDst, so only unserved ports reach the guard. Layer 3 services are unaffected: their traffic is released to the host in injectInbound and never reaches acceptTCP. Fixes #20362 Signed-off-by: kevinliang10 <kevinliang@tailscale.com>
This commit is contained in:
@@ -1683,6 +1683,17 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
|
||||
// here instead.
|
||||
r.Complete(true) // sends a RST
|
||||
return
|
||||
case ns.isVIPServiceIP(dialIP):
|
||||
// TCP to a VIP service IP on a port the service does not serve. A served
|
||||
// port returns early above (TCPHandlerForDst is non-nil), so reaching here
|
||||
// means this node has no serve handler for this port. Don't fall through
|
||||
// to the isTailscaleIP case below (a VIP is in the Tailscale IP range),
|
||||
// which would rewrite the dial target to 127.0.0.1:<port> and forwardTCP
|
||||
// the connection onto whatever unrelated service happens to be listening
|
||||
// on the host's loopback at that port — reachable via the service IP by
|
||||
// any peer, even one granted access only to the service. Reject with a RST.
|
||||
r.Complete(true) // sends a RST
|
||||
return
|
||||
case isTailscaleIP:
|
||||
dialIP = ipv4Loopback
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user