fix: mitigate windows daemon bypass fw crashes with appID cache

This commit is contained in:
zaneschepke
2026-03-28 05:25:06 -04:00
parent f4389e277c
commit 65719ead84
4 changed files with 50 additions and 31 deletions
@@ -16,7 +16,7 @@ data class TunnelsUiState(
data class TunnelUiItem(val config: TunnelConfig, val status: TunnelStatus? = null) {
val isRunning: Boolean
get() = status?.state != TunnelState.DOWN && status != null
get() = status?.state != TunnelState.DOWN && status?.state != TunnelState.STOPPING && status != null
val stateColor: Color
get() = status?.state?.asColor() ?: TunnelState.DOWN.asColor()
+3 -3
View File
@@ -9,16 +9,16 @@ require (
github.com/cenkalti/backoff/v5 v5.0.3
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466
github.com/google/nftables v0.3.0
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6
github.com/vishvananda/netlink v1.3.1
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.zx2c4.com/wireguard/windows v0.5.3
inet.af/wf v0.0.0-20221017222439-36129f591884
tailscale.com v1.94.1
)
require (
github.com/AdguardTeam/golibs v0.35.7 // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/ameshkov/dnscrypt/v2 v2.4.0 // indirect
github.com/ameshkov/dnsstamps v1.0.3 // indirect
github.com/google/go-cmp v0.7.0 // indirect
@@ -28,7 +28,7 @@ require (
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
golang.org/x/exp/typeparams v0.0.0-20260112195511-716be5621a96 // indirect
golang.org/x/exp/typeparams v0.0.0-20251125195548-87e1e737ad39 // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/tools v0.41.0 // indirect
+6 -6
View File
@@ -2,8 +2,8 @@ github.com/AdguardTeam/dnsproxy v0.78.2 h1:g+ba4vh72hAv9zIE+OPSEnu77utSKxIF6u2jN
github.com/AdguardTeam/dnsproxy v0.78.2/go.mod h1:gwr+7Dc0e7QddQLC9JLGjL5NSKcqw0ESsNMRI5Q67Ps=
github.com/AdguardTeam/golibs v0.35.7 h1:pTQpixUos7mALr3jqb0pigfrkiqPAX1hiYUi/yeBWiA=
github.com/AdguardTeam/golibs v0.35.7/go.mod h1:meFdRqMtG/PLW6LD20MYAlcRbwAVowlbunHgE17xz9s=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A=
github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM=
github.com/ameshkov/dnscrypt/v2 v2.4.0 h1:if6ZG2cuQmcP2TwSY+D0+8+xbPfoatufGlOQTMNkI9o=
@@ -38,6 +38,8 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M=
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y=
github.com/things-go/go-socks5 v0.1.0 h1:4f5dz0iMQ6cA4wseFmyLmCHmg3SWJTW92ndrKS6oERg=
github.com/things-go/go-socks5 v0.1.0/go.mod h1:Riabiyu52kLsla0YmJqunt1c1JEl6iXSr4bRd7swFEA=
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
@@ -58,8 +60,8 @@ golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
golang.org/x/exp/typeparams v0.0.0-20260112195511-716be5621a96 h1:RMc8anw0hCPcg5CZYN2PEQ8nMwosk461R6vFwPrCFVg=
golang.org/x/exp/typeparams v0.0.0-20260112195511-716be5621a96/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
golang.org/x/exp/typeparams v0.0.0-20251125195548-87e1e737ad39 h1:yzGKB4T4r1nFi65o7dQ96ERTfU2trk8Ige9aqqADqf4=
golang.org/x/exp/typeparams v0.0.0-20251125195548-87e1e737ad39/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
@@ -89,7 +91,5 @@ gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvs
gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM=
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0 h1:5SXjd4ET5dYijLaf0O3aOenC0Z4ZafIWSpjUzsQaNho=
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0/go.mod h1:EPDDhEZqVHhWuPI5zPAsjU0U7v9xNIWjoOVyZ5ZcniQ=
inet.af/wf v0.0.0-20221017222439-36129f591884 h1:zg9snq3Cpy50lWuVqDYM7AIRVTtU50y5WXETMFohW/Q=
inet.af/wf v0.0.0-20221017222439-36129f591884/go.mod h1:bSAQ38BYbY68uwpasXOTZo22dKGy9SNvI6PZFeKomZE=
tailscale.com v1.94.1 h1:0dAst/ozTuFkgmxZULc3oNwR9+qPIt5ucvzH7kaM0Jw=
tailscale.com v1.94.1/go.mod h1:gLnVrEOP32GWvroaAHHGhjSGMPJ1i4DvqNwEg+Yuov4=
@@ -14,10 +14,10 @@ import (
"sync/atomic"
"github.com/amnezia-vpn/amneziawg-go/device"
"github.com/tailscale/wf"
"github.com/wgtunnel/desktop/tunnel/vpn/firewall"
"golang.org/x/net/nettest"
"golang.org/x/sys/windows"
"inet.af/wf"
"tailscale.com/net/netaddr"
)
@@ -32,9 +32,8 @@ type WindowsFirewall struct {
iface string
luid uint64
appID string
luid uint64
appID string
killSwitchEnabled atomic.Bool
persistKillSwitch atomic.Bool
@@ -94,6 +93,11 @@ func New(logger *device.Logger) (firewall.Firewall, error) {
return nil, err
}
// try to cache the app id once to prevent https://github.com/tailscale/wf/issues/25
if err := f.cacheAppID(); err != nil {
f.logger.Errorf("failed to cache daemon AppID: %v", err)
}
return f, nil
}
@@ -234,6 +238,22 @@ func (f *WindowsFirewall) IsEnabled() bool {
return f.killSwitchEnabled.Load()
}
func (f *WindowsFirewall) cacheAppID() error {
currentFile, err := os.Executable()
if err != nil {
return err
}
appID, err := wf.AppID(currentFile)
if err != nil {
return fmt.Errorf("could not get app id for %q: %w", currentFile, err)
}
f.appID = appID
f.logger.Verbosef("Cached daemon AppID: %s (will never call wf.AppID again)", appID)
return nil
}
func (f *WindowsFirewall) RemoveTunnelRules() error {
tunRulesCopy := make([]*wf.Rule, len(f.tunRules))
copy(tunRulesCopy, f.tunRules)
@@ -369,27 +389,26 @@ func (f *WindowsFirewall) Disable() error {
return nil
}
// permitDaemon allows the daemon process through firewall
func (f *WindowsFirewall) permitDaemon(w weight) error {
return f.addDaemonRule()
}
currentFile, err := os.Executable()
if err != nil {
return err
// addDaemonRule add the daemon bypass using the cached AppID
func (f *WindowsFirewall) addDaemonRule() error {
if f.appID == "" {
return fmt.Errorf("AppID not cached")
}
appID, err := wf.AppID(currentFile)
f.logger.Verbosef("Adding bypass rule for %s", appID)
if err != nil {
return fmt.Errorf("could not get app id for %q: %w", currentFile, err)
}
conditions := []*wf.Match{
{
Field: wf.FieldALEAppID,
Op: wf.MatchTypeEqual,
Value: appID,
},
}
_, err = f.addRules("unrestricted traffic for daemon", w, conditions, wf.ActionPermit, protocolAll, directionBoth)
f.logger.Verbosef("Adding bypass rule for daemon (using cached AppID)")
conditions := []*wf.Match{{
Field: wf.FieldALEAppID,
Op: wf.MatchTypeEqual,
Value: f.appID,
}}
_, err := f.addRules("unrestricted traffic for daemon", weightDaemonTraffic,
conditions, wf.ActionPermit, protocolAll, directionBoth)
return err
}