diff options
| author | David Lönnhager <david.l@mullvad.net> | 2025-08-14 11:29:51 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2025-08-21 14:58:30 +0200 |
| commit | e3d0cab9bbb56af9a1e92da0a5040365bb0917d3 (patch) | |
| tree | 7f2da7c81efd87fc7babe93763f606afb7936895 | |
| parent | dcb3f3d11b0ede581800d7a1a63ff56ee636b0bb (diff) | |
| download | mullvadvpn-e3d0cab9bbb56af9a1e92da0a5040365bb0917d3.tar.xz mullvadvpn-e3d0cab9bbb56af9a1e92da0a5040365bb0917d3.zip | |
Block traffic to exit hop from non-relay client process in WinFw
This fixes an issue where traffic could leak unencrypted from the entry
hop to the exit hop IP when using multihop
| -rw-r--r-- | talpid-core/src/firewall/mod.rs | 6 | ||||
| -rw-r--r-- | talpid-core/src/firewall/windows/mod.rs | 25 | ||||
| -rw-r--r-- | talpid-core/src/firewall/windows/winfw/mod.rs | 34 | ||||
| -rw-r--r-- | talpid-core/src/firewall/windows/winfw/sys.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 11 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connecting_state.rs | 8 | ||||
| -rw-r--r-- | windows/winfw/src/winfw/fwcontext.cpp | 34 | ||||
| -rw-r--r-- | windows/winfw/src/winfw/fwcontext.h | 2 | ||||
| -rw-r--r-- | windows/winfw/src/winfw/mullvadguids.cpp | 62 | ||||
| -rw-r--r-- | windows/winfw/src/winfw/mullvadguids.h | 4 | ||||
| -rw-r--r-- | windows/winfw/src/winfw/rules/baseline/permitvpntunnel.cpp | 133 | ||||
| -rw-r--r-- | windows/winfw/src/winfw/rules/baseline/permitvpntunnel.h | 11 | ||||
| -rw-r--r-- | windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.cpp | 132 | ||||
| -rw-r--r-- | windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.h | 7 | ||||
| -rw-r--r-- | windows/winfw/src/winfw/winfw.cpp | 20 | ||||
| -rw-r--r-- | windows/winfw/src/winfw/winfw.h | 13 |
16 files changed, 465 insertions, 39 deletions
diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs index d72a70083e..22d4954d64 100644 --- a/talpid-core/src/firewall/mod.rs +++ b/talpid-core/src/firewall/mod.rs @@ -83,6 +83,9 @@ pub enum FirewallPolicy { Connecting { /// The peer endpoint that should be allowed. peer_endpoint: AllowedEndpoint, + /// IP of the exit endpoint, iff it differs from `peer_endpoint` + #[cfg(target_os = "windows")] + exit_endpoint_ip: Option<IpAddr>, /// Metadata about the tunnel and tunnel interface. tunnel: Option<crate::tunnel::TunnelMetadata>, /// Flag setting if communication with LAN networks should be possible. @@ -100,6 +103,9 @@ pub enum FirewallPolicy { Connected { /// The peer endpoint that should be allowed. peer_endpoint: AllowedEndpoint, + /// IP of the exit endpoint, iff it differs from `peer_endpoint` + #[cfg(target_os = "windows")] + exit_endpoint_ip: Option<IpAddr>, /// Metadata about the tunnel and tunnel interface. tunnel: crate::tunnel::TunnelMetadata, /// Flag setting if communication with LAN networks should be possible. diff --git a/talpid-core/src/firewall/windows/mod.rs b/talpid-core/src/firewall/windows/mod.rs index ad9af12df3..20d8901c4a 100644 --- a/talpid-core/src/firewall/windows/mod.rs +++ b/talpid-core/src/firewall/windows/mod.rs @@ -125,15 +125,16 @@ impl Firewall { let apply_result = match policy { FirewallPolicy::Connecting { peer_endpoint, + exit_endpoint_ip, tunnel, allow_lan, allowed_endpoint, allowed_tunnel_traffic, } => { let cfg = &WinFwSettings::new(allow_lan); - self.set_connecting_state( &peer_endpoint, + exit_endpoint_ip, cfg, tunnel.as_ref(), allowed_endpoint, @@ -142,12 +143,19 @@ impl Firewall { } FirewallPolicy::Connected { peer_endpoint, + exit_endpoint_ip, tunnel, allow_lan, dns_config, } => { let cfg = &WinFwSettings::new(allow_lan); - self.set_connected_state(&peer_endpoint, cfg, &tunnel, &dns_config) + self.set_connected_state( + &peer_endpoint, + exit_endpoint_ip, + cfg, + &tunnel, + &dns_config, + ) } FirewallPolicy::Blocked { allow_lan, @@ -192,6 +200,7 @@ impl Firewall { fn set_connecting_state( &mut self, peer_endpoint: &AllowedEndpoint, + exit_endpoint_ip: Option<IpAddr>, winfw_settings: &WinFwSettings, tunnel_metadata: Option<&TunnelMetadata>, allowed_endpoint: AllowedEndpoint, @@ -201,6 +210,7 @@ impl Firewall { let tunnel_interface = tunnel_metadata.map(|metadata| metadata.interface.as_ref()); winfw::apply_policy_connecting( peer_endpoint, + exit_endpoint_ip, winfw_settings, tunnel_interface, allowed_endpoint, @@ -212,14 +222,21 @@ impl Firewall { fn set_connected_state( &mut self, endpoint: &AllowedEndpoint, + exit_endpoint_ip: Option<IpAddr>, winfw_settings: &WinFwSettings, tunnel_metadata: &TunnelMetadata, dns_config: &ResolvedDnsConfig, ) -> Result<(), Error> { log::trace!("Applying 'connected' firewall policy"); let tunnel_interface = &tunnel_metadata.interface; - winfw::apply_policy_connected(endpoint, winfw_settings, tunnel_interface, dns_config) - .map_err(Error::ApplyingConnectedPolicy) + winfw::apply_policy_connected( + endpoint, + exit_endpoint_ip, + winfw_settings, + tunnel_interface, + dns_config, + ) + .map_err(Error::ApplyingConnectedPolicy) } fn set_blocked_state( diff --git a/talpid-core/src/firewall/windows/winfw/mod.rs b/talpid-core/src/firewall/windows/winfw/mod.rs index b6850754a8..9a2d5a7c8a 100644 --- a/talpid-core/src/firewall/windows/winfw/mod.rs +++ b/talpid-core/src/firewall/windows/winfw/mod.rs @@ -1,7 +1,7 @@ //! Safe bindings for the WinFW library. use super::{AllowedEndpoint, AllowedTunnelTraffic, Error, WideCString, widestring_ip}; -use std::ptr; +use std::{net::IpAddr, ptr}; use talpid_types::{net::TransportProtocol, tunnel::FirewallPolicyError}; mod sys; @@ -78,10 +78,7 @@ pub(super) fn apply_policy_blocked( let allowed_endpoint = allowed_endpoint .as_ref() .map(WinFwAllowedEndpointContainer::as_endpoint); - let allowed_endpoint_ptr = allowed_endpoint - .as_ref() - .map(ptr::from_ref) - .unwrap_or(ptr::null()); + let allowed_endpoint_ptr = allowed_endpoint.as_ref().map_or(ptr::null(), ptr::from_ref); // SAFETY: This function is always safe to call let application = unsafe { WinFw_ApplyPolicyBlocked(winfw_settings, allowed_endpoint_ptr) }; application.into_result() @@ -89,6 +86,7 @@ pub(super) fn apply_policy_blocked( pub(super) fn apply_policy_connecting( peer_endpoint: &AllowedEndpoint, + exit_endpoint_ip: Option<IpAddr>, winfw_settings: &WinFwSettings, tunnel_interface: Option<&str>, allowed_endpoint: AllowedEndpoint, @@ -101,9 +99,6 @@ pub(super) fn apply_policy_connecting( protocol: WinFwProt::from(peer_endpoint.endpoint.protocol), }; - // SAFETY: `endpoint1_ip`, `endpoint2_ip`, `endpoint1`, `endpoint2`, `relay_client_wstrs` - // must not be dropped until `WinFw_ApplyPolicyConnecting` has returned. - let relay_client_wstrs: Vec<_> = peer_endpoint .clients .iter() @@ -171,11 +166,17 @@ pub(super) fn apply_policy_connecting( .unwrap_or(ptr::null()), }; + let exit_endpoint_ip_wstr = exit_endpoint_ip.map(widestring_ip); + let exit_endpoint_ip_ptr = exit_endpoint_ip_wstr + .as_ref() + .map_or(ptr::null(), |ip| ip.as_ptr()); + #[allow(clippy::undocumented_unsafe_blocks)] // Remove me if you dare. let res = unsafe { WinFw_ApplyPolicyConnecting( winfw_settings, &winfw_relay, + exit_endpoint_ip_ptr, relay_client_wstr_ptrs.as_ptr(), relay_client_wstr_ptrs_len, interface_wstr_ptr, @@ -183,9 +184,7 @@ pub(super) fn apply_policy_connecting( &allowed_tunnel_traffic, ) }; - // SAFETY: All of these hold stack allocated memory which is pointed to by - // `allowed_tunnel_traffic` and must remain allocated until `WinFw_ApplyPolicyConnecting` - // has returned. + // SAFETY: All of these must remain allocated until `WinFw_ApplyPolicyConnecting` has returned. drop(endpoint1_ip); drop(endpoint2_ip); #[allow(clippy::drop_non_drop)] @@ -193,11 +192,13 @@ pub(super) fn apply_policy_connecting( #[allow(clippy::drop_non_drop)] drop(endpoint2); drop(relay_client_wstrs); + drop(exit_endpoint_ip_wstr); res.into_result() } pub(super) fn apply_policy_connected( endpoint: &AllowedEndpoint, + exit_endpoint_ip: Option<IpAddr>, winfw_settings: &WinFwSettings, tunnel_interface: &str, dns_config: &crate::dns::ResolvedDnsConfig, @@ -245,11 +246,17 @@ pub(super) fn apply_policy_connected( .map(|ip| ip.as_ptr()) .collect(); + let exit_endpoint_ip_wstr = exit_endpoint_ip.map(widestring_ip); + let exit_endpoint_ip_ptr = exit_endpoint_ip_wstr + .as_ref() + .map_or(ptr::null(), |ip| ip.as_ptr()); + #[allow(clippy::undocumented_unsafe_blocks)] // Remove me if you dare. let result = unsafe { WinFw_ApplyPolicyConnected( winfw_settings, &winfw_relay, + exit_endpoint_ip_ptr, relay_client_wstr_ptrs.as_ptr(), relay_client_wstr_ptrs_len, tunnel_alias.as_ptr(), @@ -260,8 +267,9 @@ pub(super) fn apply_policy_connected( ) }; - // SAFETY: `relay_client_wstrs` holds memory pointed to by pointers used in C++ and must - // not be dropped until after `WinFw_ApplyPolicyConnected` has returned. + // SAFETY: All of these must remain allocated until `WinFw_ApplyPolicyConnected` has returned. + drop(exit_endpoint_ip_wstr); + drop(ip_str); drop(relay_client_wstrs); result.into_result() } diff --git a/talpid-core/src/firewall/windows/winfw/sys.rs b/talpid-core/src/firewall/windows/winfw/sys.rs index 964f4c4d51..c320181d75 100644 --- a/talpid-core/src/firewall/windows/winfw/sys.rs +++ b/talpid-core/src/firewall/windows/winfw/sys.rs @@ -134,6 +134,7 @@ unsafe extern "system" { pub fn WinFw_ApplyPolicyConnecting( settings: &WinFwSettings, relay: &WinFwEndpoint, + exitEndpointIp: *const libc::wchar_t, relayClient: *const *const libc::wchar_t, relayClientLen: usize, tunnelIfaceAlias: *const libc::wchar_t, @@ -145,6 +146,7 @@ unsafe extern "system" { pub fn WinFw_ApplyPolicyConnected( settings: &WinFwSettings, relay: &WinFwEndpoint, + exitEndpointIp: *const libc::wchar_t, relayClient: *const *const libc::wchar_t, relayClientLen: usize, tunnelIfaceAlias: *const libc::wchar_t, diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index b671095fb3..12767d7d65 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -124,6 +124,15 @@ impl ConnectedState { AllowedClients::Root }; + #[cfg(target_os = "windows")] + let exit_endpoint_ip = self + .tunnel_parameters + .get_exit_hop_endpoint() + .map(|ep| ep.address.ip()); + + #[cfg(target_os = "windows")] + debug_assert_ne!(exit_endpoint_ip, Some(endpoint.address.ip())); + let peer_endpoint = AllowedEndpoint { endpoint, clients }; #[cfg(target_os = "macos")] @@ -133,6 +142,8 @@ impl ConnectedState { FirewallPolicy::Connected { peer_endpoint, + #[cfg(target_os = "windows")] + exit_endpoint_ip, tunnel: self.metadata.clone(), allow_lan: shared_values.allow_lan, #[cfg(not(target_os = "android"))] diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index 8f26c92b9d..9c89b3fc3d 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -183,6 +183,12 @@ impl ConnectingState { AllowedClients::Root }; + #[cfg(target_os = "windows")] + let exit_endpoint_ip = params.get_exit_hop_endpoint().map(|ep| ep.address.ip()); + + #[cfg(target_os = "windows")] + debug_assert_ne!(exit_endpoint_ip, Some(endpoint.address.ip())); + let peer_endpoint = AllowedEndpoint { endpoint, clients }; #[cfg(target_os = "macos")] @@ -192,6 +198,8 @@ impl ConnectingState { let policy = FirewallPolicy::Connecting { peer_endpoint, + #[cfg(target_os = "windows")] + exit_endpoint_ip, tunnel: tunnel_metadata.clone(), allow_lan: shared_values.allow_lan, allowed_endpoint: shared_values.allowed_endpoint.clone(), diff --git a/windows/winfw/src/winfw/fwcontext.cpp b/windows/winfw/src/winfw/fwcontext.cpp index 4ed22737fc..7747d7c822 100644 --- a/windows/winfw/src/winfw/fwcontext.cpp +++ b/windows/winfw/src/winfw/fwcontext.cpp @@ -185,6 +185,7 @@ bool FwContext::applyPolicyConnecting ( const WinFwSettings &settings, const WinFwEndpoint &relay, + const std::optional<wfp::IpAddress> &exitEndpointIp, const std::vector<std::wstring> &relayClients, const std::optional<std::wstring> &tunnelInterfaceAlias, const std::optional<WinFwAllowedEndpoint> &allowedEndpoint, @@ -209,12 +210,16 @@ bool FwContext::applyPolicyConnecting case WinFwAllowedTunnelTrafficType::All: { ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnel>( + relayClients, *tunnelInterfaceAlias, - std::nullopt + std::nullopt, + exitEndpointIp )); ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnelService>( + relayClients, *tunnelInterfaceAlias, - std::nullopt + std::nullopt, + exitEndpointIp )); break; } @@ -229,12 +234,16 @@ bool FwContext::applyPolicyConnecting std::nullopt, }); ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnel>( + relayClients, *tunnelInterfaceAlias, - onlyEndpoint + onlyEndpoint, + exitEndpointIp )); ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnelService>( + relayClients, *tunnelInterfaceAlias, - onlyEndpoint + onlyEndpoint, + exitEndpointIp )); break; } @@ -253,12 +262,16 @@ bool FwContext::applyPolicyConnecting }) }); ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnel>( + relayClients, *tunnelInterfaceAlias, - endpoints + endpoints, + exitEndpointIp )); ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnelService>( + relayClients, *tunnelInterfaceAlias, - endpoints + endpoints, + exitEndpointIp )); break; } @@ -280,6 +293,7 @@ bool FwContext::applyPolicyConnected ( const WinFwSettings &settings, const WinFwEndpoint &relay, + const std::optional<wfp::IpAddress> &exitEndpointIp, const std::vector<std::wstring> &relayClient, const std::wstring &tunnelInterfaceAlias, const std::vector<wfp::IpAddress> &tunnelDnsServers, @@ -306,13 +320,17 @@ bool FwContext::applyPolicyConnected } ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnel>( + relayClient, tunnelInterfaceAlias, - std::nullopt + std::nullopt, + exitEndpointIp )); ruleset.emplace_back(std::make_unique<baseline::PermitVpnTunnelService>( + relayClient, tunnelInterfaceAlias, - std::nullopt + std::nullopt, + exitEndpointIp )); const auto status = applyRuleset(ruleset); diff --git a/windows/winfw/src/winfw/fwcontext.h b/windows/winfw/src/winfw/fwcontext.h index 92ecce4f4f..8a4e4f0301 100644 --- a/windows/winfw/src/winfw/fwcontext.h +++ b/windows/winfw/src/winfw/fwcontext.h @@ -28,6 +28,7 @@ public: ( const WinFwSettings &settings, const WinFwEndpoint &relay, + const std::optional<wfp::IpAddress> &exitEndpointIp, const std::vector<std::wstring> &relayClients, const std::optional<std::wstring> &tunnelInterfaceAlias, const std::optional<WinFwAllowedEndpoint> &allowedEndpoint, @@ -38,6 +39,7 @@ public: ( const WinFwSettings &settings, const WinFwEndpoint &relay, + const std::optional<wfp::IpAddress> &exitEndpointIp, const std::vector<std::wstring> &relayClients, const std::wstring &tunnelInterfaceAlias, const std::vector<wfp::IpAddress> &tunnelDnsServers, diff --git a/windows/winfw/src/winfw/mullvadguids.cpp b/windows/winfw/src/winfw/mullvadguids.cpp index 890c8b0c2e..3029607a11 100644 --- a/windows/winfw/src/winfw/mullvadguids.cpp +++ b/windows/winfw/src/winfw/mullvadguids.cpp @@ -134,10 +134,14 @@ MullvadGuids::DetailedIdentityRegistry MullvadGuids::DetailedRegistry(IdentityQu registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnel_Outbound_Ipv6_1())); registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnel_Outbound_Ipv4_2())); registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnel_Outbound_Ipv6_2())); + registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnel_ExitIp())); + registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnel_BlockExitIp())); registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnelService_Ipv4_1())); registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnelService_Ipv6_1())); registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnelService_Ipv4_2())); registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnelService_Ipv6_2())); + registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnelService_ExitIp())); + registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitVpnTunnelService_BlockExitIp())); registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitNdp_Outbound_Router_Solicitation())); registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitNdp_Inbound_Router_Advertisement())); registry.insert(std::make_pair(WfpObjectType::Filter, Filter_Baseline_PermitNdp_Outbound_Neighbor_Solicitation())); @@ -723,6 +727,34 @@ const GUID &MullvadGuids::Filter_Baseline_PermitVpnTunnel_Outbound_Ipv6_2() } //static +const GUID &MullvadGuids::Filter_Baseline_PermitVpnTunnel_ExitIp() +{ + static const GUID g = + { + 0x1769c66f, + 0xa5ef, + 0x1128, + { 0xb1, 0x5e, 0xd9, 0xe2, 0x6e, 0xf4, 0xf0, 0x06 } + }; + + return g; +} + +//static +const GUID &MullvadGuids::Filter_Baseline_PermitVpnTunnel_BlockExitIp() +{ + static const GUID g = + { + 0x276bc66f, + 0xf5ef, + 0x1128, + { 0xb1, 0x5e, 0xd9, 0xe2, 0x6e, 0xf4, 0xf0, 0x06 } + }; + + return g; +} + +//static const GUID &MullvadGuids::Filter_Baseline_PermitVpnTunnelService_Ipv4_1() { static const GUID g = @@ -780,6 +812,36 @@ const GUID &MullvadGuids::Filter_Baseline_PermitVpnTunnelService_Ipv6_2() } //static +const GUID &MullvadGuids::Filter_Baseline_PermitVpnTunnelService_ExitIp() +{ + + static const GUID g = + { + 0x01deb41a, + 0xb25d, + 0x1e60, + { 0xef, 0x12, 0xef, 0x3b, 0x40, 0xc0, 0x8e, 0xdc } + }; + + return g; +} + +//static +const GUID &MullvadGuids::Filter_Baseline_PermitVpnTunnelService_BlockExitIp() +{ + + static const GUID g = + { + 0x01deb2b8, + 0xb25d, + 0x1e60, + { 0xef, 0x52, 0xef, 0x3b, 0x40, 0xc0, 0x8e, 0xdc } + }; + + return g; +} + +//static const GUID &MullvadGuids::Filter_Baseline_PermitNdp_Outbound_Router_Solicitation() { static const GUID g = diff --git a/windows/winfw/src/winfw/mullvadguids.h b/windows/winfw/src/winfw/mullvadguids.h index d63af0206d..e2fd7ab276 100644 --- a/windows/winfw/src/winfw/mullvadguids.h +++ b/windows/winfw/src/winfw/mullvadguids.h @@ -75,11 +75,15 @@ public: static const GUID &Filter_Baseline_PermitVpnTunnel_Outbound_Ipv6_1(); static const GUID &Filter_Baseline_PermitVpnTunnel_Outbound_Ipv4_2(); static const GUID &Filter_Baseline_PermitVpnTunnel_Outbound_Ipv6_2(); + static const GUID &Filter_Baseline_PermitVpnTunnel_ExitIp(); + static const GUID &Filter_Baseline_PermitVpnTunnel_BlockExitIp(); static const GUID &Filter_Baseline_PermitVpnTunnelService_Ipv4_1(); static const GUID &Filter_Baseline_PermitVpnTunnelService_Ipv6_1(); static const GUID &Filter_Baseline_PermitVpnTunnelService_Ipv4_2(); static const GUID &Filter_Baseline_PermitVpnTunnelService_Ipv6_2(); + static const GUID &Filter_Baseline_PermitVpnTunnelService_ExitIp(); + static const GUID &Filter_Baseline_PermitVpnTunnelService_BlockExitIp(); static const GUID &Filter_Baseline_PermitNdp_Outbound_Router_Solicitation(); static const GUID &Filter_Baseline_PermitNdp_Inbound_Router_Advertisement(); diff --git a/windows/winfw/src/winfw/rules/baseline/permitvpntunnel.cpp b/windows/winfw/src/winfw/rules/baseline/permitvpntunnel.cpp index 3aaff601a3..5ee9c2d966 100644 --- a/windows/winfw/src/winfw/rules/baseline/permitvpntunnel.cpp +++ b/windows/winfw/src/winfw/rules/baseline/permitvpntunnel.cpp @@ -4,6 +4,8 @@ #include <winfw/rules/shared.h> #include <libwfp/filterbuilder.h> #include <libwfp/conditionbuilder.h> +#include <libwfp/conditions/comparison.h> +#include <libwfp/conditions/conditionapplication.h> #include <libwfp/conditions/conditioninterface.h> #include <libwfp/conditions/conditionip.h> #include <libwfp/conditions/conditionport.h> @@ -16,11 +18,15 @@ namespace rules::baseline { PermitVpnTunnel::PermitVpnTunnel( + const std::vector<std::wstring> &relayClients, const std::wstring &tunnelInterfaceAlias, - const std::optional<Endpoints> &potentialEndpoints + const std::optional<Endpoints> &potentialEndpoints, + const std::optional<wfp::IpAddress> &exitEndpointIp ) - : m_tunnelInterfaceAlias(tunnelInterfaceAlias) + : m_relayClients(relayClients) + , m_tunnelInterfaceAlias(tunnelInterfaceAlias) , m_potentialEndpoints(potentialEndpoints) + , m_exitEndpointIp(exitEndpointIp) { } @@ -86,8 +92,125 @@ bool PermitVpnTunnel::AddEndpointFilter(const std::optional<Endpoint> &endpoint, } +bool PermitVpnTunnel::BlockNonRelayClientExit(const wfp::IpAddress &exitIp, IObjectInstaller &objectInstaller) +{ + if (m_relayClients.empty()) + { + // If "relay clients" is empty, then permit connections to exit from any process + return true; + } + + wfp::FilterBuilder filterBuilder; + + // + // Permit traffic to exit relay from relay clients + // + + filterBuilder + .description(L"This filter is part of a rule that allows exit IP traffic from select clients") + .name(L"Permit inbound exit relay connections on tunnel interface") + .provider(MullvadGuids::Provider()) + .sublayer(MullvadGuids::SublayerBaseline()) + .key(MullvadGuids::Filter_Baseline_PermitVpnTunnel_ExitIp()) + .weight(wfp::FilterBuilder::WeightClass::Max) + .permit(); + + if (exitIp.type() == wfp::IpAddress::Ipv4) + { + filterBuilder.layer(FWPM_LAYER_ALE_AUTH_CONNECT_V4); + + wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_CONNECT_V4); + + conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias)); + conditionBuilder.add_condition(ConditionIp::Remote(exitIp)); + + for (auto relayClient : m_relayClients) { + conditionBuilder.add_condition(std::make_unique<ConditionApplication>(relayClient)); + } + + if (!objectInstaller.addFilter(filterBuilder, conditionBuilder)) + { + return false; + } + } + else + { + filterBuilder.layer(FWPM_LAYER_ALE_AUTH_CONNECT_V6); + + wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_CONNECT_V6); + + conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias)); + conditionBuilder.add_condition(ConditionIp::Remote(exitIp)); + + for (auto relayClient : m_relayClients) { + conditionBuilder.add_condition(std::make_unique<ConditionApplication>(relayClient)); + } + + if (!objectInstaller.addFilter(filterBuilder, conditionBuilder)) + { + return false; + } + } + + // + // Block all remaining traffic to the exit + // + + { + wfp::FilterBuilder filterBuilder; + + filterBuilder + .description(L"This filter is part of a rule that blocks exit IP traffic from unexpected clients") + .name(L"Block inbound exit relay connections on tunnel interface") + .provider(MullvadGuids::Provider()) + .sublayer(MullvadGuids::SublayerBaseline()) + .key(MullvadGuids::Filter_Baseline_PermitVpnTunnel_BlockExitIp()) + .weight(wfp::FilterBuilder::WeightClass::Max) + .block(); + + if (exitIp.type() == wfp::IpAddress::Ipv4) + { + filterBuilder.layer(FWPM_LAYER_ALE_AUTH_CONNECT_V4); + + wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_CONNECT_V4); + + conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias)); + conditionBuilder.add_condition(ConditionIp::Remote(exitIp)); + + if (!objectInstaller.addFilter(filterBuilder, conditionBuilder)) + { + return false; + } + } + else + { + filterBuilder.layer(FWPM_LAYER_ALE_AUTH_CONNECT_V6); + + wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_CONNECT_V6); + + conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias)); + conditionBuilder.add_condition(ConditionIp::Remote(exitIp)); + + if (!objectInstaller.addFilter(filterBuilder, conditionBuilder)) + { + return false; + } + } + } + + return true; +} + + bool PermitVpnTunnel::apply(IObjectInstaller &objectInstaller) { + if (m_exitEndpointIp.has_value()) + { + if (!BlockNonRelayClientExit(m_exitEndpointIp.value(), objectInstaller)) + { + return false; + } + } if (!m_potentialEndpoints.has_value()) { return AddEndpointFilter( @@ -98,15 +221,15 @@ bool PermitVpnTunnel::apply(IObjectInstaller &objectInstaller) ); } AddEndpointFilter( - std::make_optional<Endpoint>(m_potentialEndpoints.value().entryEndpoint), + std::make_optional<Endpoint>(m_potentialEndpoints.value().endpoint1), MullvadGuids::Filter_Baseline_PermitVpnTunnel_Outbound_Ipv4_1(), MullvadGuids::Filter_Baseline_PermitVpnTunnel_Outbound_Ipv6_1(), objectInstaller ); - if (m_potentialEndpoints.value().exitEndpoint.has_value()) + if (m_potentialEndpoints.value().endpoint2.has_value()) { AddEndpointFilter( - m_potentialEndpoints.value().exitEndpoint.value(), + m_potentialEndpoints.value().endpoint2.value(), MullvadGuids::Filter_Baseline_PermitVpnTunnel_Outbound_Ipv4_2(), MullvadGuids::Filter_Baseline_PermitVpnTunnel_Outbound_Ipv6_2(), objectInstaller diff --git a/windows/winfw/src/winfw/rules/baseline/permitvpntunnel.h b/windows/winfw/src/winfw/rules/baseline/permitvpntunnel.h index a89ac46d98..e8446f3e35 100644 --- a/windows/winfw/src/winfw/rules/baseline/permitvpntunnel.h +++ b/windows/winfw/src/winfw/rules/baseline/permitvpntunnel.h @@ -20,22 +20,27 @@ public: }; struct Endpoints { - Endpoint entryEndpoint; - std::optional<Endpoint> exitEndpoint; + Endpoint endpoint1; + std::optional<Endpoint> endpoint2; }; PermitVpnTunnel( + const std::vector<std::wstring> &relayClients, const std::wstring &tunnelInterfaceAlias, - const std::optional<Endpoints> &potentialEndpoints + const std::optional<Endpoints> &potentialEndpoints, + const std::optional<wfp::IpAddress> &exitEndpoint ); bool apply(IObjectInstaller &objectInstaller) override; private: bool AddEndpointFilter(const std::optional<Endpoint> &endpoint, const GUID &ipv4Guid, const GUID &ipv6Guid, IObjectInstaller &objectInstaller); + bool BlockNonRelayClientExit(const wfp::IpAddress &exitIp, IObjectInstaller &objectInstaller); + const std::vector<std::wstring> m_relayClients; const std::wstring m_tunnelInterfaceAlias; const std::optional<Endpoints> m_potentialEndpoints; + const std::optional<wfp::IpAddress> m_exitEndpointIp; }; } diff --git a/windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.cpp b/windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.cpp index e5b7ab1f5c..b185dccbd2 100644 --- a/windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.cpp +++ b/windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.cpp @@ -4,6 +4,8 @@ #include <winfw/rules/shared.h> #include <libwfp/filterbuilder.h> #include <libwfp/conditionbuilder.h> +#include <libwfp/conditions/comparison.h> +#include <libwfp/conditions/conditionapplication.h> #include <libwfp/conditions/conditioninterface.h> #include <libwfp/conditions/conditionip.h> #include <libwfp/conditions/conditionport.h> @@ -17,11 +19,15 @@ namespace rules::baseline using Endpoint = PermitVpnTunnel::Endpoint; PermitVpnTunnelService::PermitVpnTunnelService( + const std::vector<std::wstring> &relayClients, const std::wstring &tunnelInterfaceAlias, - const std::optional<PermitVpnTunnel::Endpoints> &potentialEndpoints + const std::optional<PermitVpnTunnel::Endpoints> &potentialEndpoints, + const std::optional<wfp::IpAddress> &exitEndpointIp ) - : m_tunnelInterfaceAlias(tunnelInterfaceAlias) + : m_relayClients(relayClients) + , m_tunnelInterfaceAlias(tunnelInterfaceAlias) , m_potentialEndpoints(potentialEndpoints) + , m_exitEndpointIp(exitEndpointIp) { } @@ -88,8 +94,124 @@ bool PermitVpnTunnelService::AddEndpointFilter(const std::optional<PermitVpnTunn return true; } +bool PermitVpnTunnelService::BlockNonRelayClientExit(const wfp::IpAddress &exitIp, IObjectInstaller &objectInstaller) +{ + if (m_relayClients.empty()) + { + // If "relay clients" is empty, then permit connections to exit from any process + return true; + } + + wfp::FilterBuilder filterBuilder; + + // + // Permit traffic to exit relay from relay clients + // + + filterBuilder + .description(L"This filter is part of a rule that allows exit IP traffic from select clients") + .name(L"Permit inbound exit relay connections on tunnel interface") + .provider(MullvadGuids::Provider()) + .sublayer(MullvadGuids::SublayerBaseline()) + .key(MullvadGuids::Filter_Baseline_PermitVpnTunnelService_ExitIp()) + .weight(wfp::FilterBuilder::WeightClass::Max) + .permit(); + + if (exitIp.type() == wfp::IpAddress::Ipv4) + { + filterBuilder.layer(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4); + + wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4); + + conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias)); + conditionBuilder.add_condition(ConditionIp::Remote(exitIp)); + + for (auto relayClient : m_relayClients) { + conditionBuilder.add_condition(std::make_unique<ConditionApplication>(relayClient)); + } + + if (!objectInstaller.addFilter(filterBuilder, conditionBuilder)) + { + return false; + } + } + else + { + filterBuilder.layer(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6); + + wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6); + + conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias)); + conditionBuilder.add_condition(ConditionIp::Remote(exitIp)); + + for (auto relayClient : m_relayClients) { + conditionBuilder.add_condition(std::make_unique<ConditionApplication>(relayClient)); + } + + if (!objectInstaller.addFilter(filterBuilder, conditionBuilder)) + { + return false; + } + } + + // + // Block all remaining traffic to the exit + // + + { + wfp::FilterBuilder filterBuilder; + + filterBuilder + .description(L"This filter is part of a rule that blocks exit IP traffic from unexpected clients") + .name(L"Block inbound exit relay connections on tunnel interface") + .provider(MullvadGuids::Provider()) + .sublayer(MullvadGuids::SublayerBaseline()) + .key(MullvadGuids::Filter_Baseline_PermitVpnTunnelService_BlockExitIp()) + .weight(wfp::FilterBuilder::WeightClass::Max) + .block(); + + if (exitIp.type() == wfp::IpAddress::Ipv4) + { + filterBuilder.layer(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4); + + wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4); + + conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias)); + conditionBuilder.add_condition(ConditionIp::Remote(exitIp)); + + if (!objectInstaller.addFilter(filterBuilder, conditionBuilder)) + { + return false; + } + } + else + { + filterBuilder.layer(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6); + + wfp::ConditionBuilder conditionBuilder(FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6); + + conditionBuilder.add_condition(ConditionInterface::Alias(m_tunnelInterfaceAlias)); + conditionBuilder.add_condition(ConditionIp::Remote(exitIp)); + + if (!objectInstaller.addFilter(filterBuilder, conditionBuilder)) + { + return false; + } + } + } + + return true; +} + bool PermitVpnTunnelService::apply(IObjectInstaller &objectInstaller) { + if (m_exitEndpointIp.has_value()) + { + if (!BlockNonRelayClientExit(m_exitEndpointIp.value(), objectInstaller)) + { + return false; + } + } if (!m_potentialEndpoints.has_value()) { return AddEndpointFilter( @@ -100,15 +222,15 @@ bool PermitVpnTunnelService::apply(IObjectInstaller &objectInstaller) ); } AddEndpointFilter( - std::make_optional<Endpoint>(m_potentialEndpoints.value().entryEndpoint), + std::make_optional<Endpoint>(m_potentialEndpoints.value().endpoint1), MullvadGuids::Filter_Baseline_PermitVpnTunnelService_Ipv4_1(), MullvadGuids::Filter_Baseline_PermitVpnTunnelService_Ipv6_1(), objectInstaller ); - if (m_potentialEndpoints.value().exitEndpoint.has_value()) + if (m_potentialEndpoints.value().endpoint2.has_value()) { AddEndpointFilter( - m_potentialEndpoints.value().exitEndpoint.value(), + m_potentialEndpoints.value().endpoint2.value(), MullvadGuids::Filter_Baseline_PermitVpnTunnelService_Ipv4_2(), MullvadGuids::Filter_Baseline_PermitVpnTunnelService_Ipv6_2(), objectInstaller diff --git a/windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.h b/windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.h index 8b21dc2264..aa2f805c62 100644 --- a/windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.h +++ b/windows/winfw/src/winfw/rules/baseline/permitvpntunnelservice.h @@ -15,17 +15,22 @@ class PermitVpnTunnelService : public IFirewallRule public: PermitVpnTunnelService( + const std::vector<std::wstring> &relayClients, const std::wstring &tunnelInterfaceAlias, - const std::optional<PermitVpnTunnel::Endpoints> &potentialEndpoints + const std::optional<PermitVpnTunnel::Endpoints> &potentialEndpoints, + const std::optional<wfp::IpAddress> &exitEndpointIp ); bool apply(IObjectInstaller &objectInstaller) override; private: bool AddEndpointFilter(const std::optional<PermitVpnTunnel::Endpoint> &endpoint, const GUID &ipv4Guid, const GUID &ipv6Guid, IObjectInstaller &objectInstaller); + bool BlockNonRelayClientExit(const wfp::IpAddress &exitIp, IObjectInstaller &objectInstaller); + const std::vector<std::wstring> m_relayClients; const std::wstring m_tunnelInterfaceAlias; const std::optional<PermitVpnTunnel::Endpoints> m_potentialEndpoints; + const std::optional<wfp::IpAddress> m_exitEndpointIp; }; } diff --git a/windows/winfw/src/winfw/winfw.cpp b/windows/winfw/src/winfw/winfw.cpp index 0ed0fe5731..d962965a19 100644 --- a/windows/winfw/src/winfw/winfw.cpp +++ b/windows/winfw/src/winfw/winfw.cpp @@ -240,6 +240,7 @@ WINFW_API WinFw_ApplyPolicyConnecting( const WinFwSettings *settings, const WinFwEndpoint *relay, + const wchar_t *exitEndpointIp, const wchar_t **relayClients, size_t relayClientsLen, const wchar_t *tunnelInterfaceAlias, @@ -269,6 +270,14 @@ WinFw_ApplyPolicyConnecting( THROW_ERROR("Invalid argument: allowedTunnelTraffic"); } + const auto exitIpAddr = (exitEndpointIp != nullptr) ? std::make_optional(wfp::IpAddress(exitEndpointIp)) : std::nullopt; + const auto entryIpAddr = wfp::IpAddress(relay->ip); + + if (entryIpAddr == exitIpAddr) + { + THROW_ERROR("Invalid argument: relay IP must not equal exitEndpointIp"); + } + std::vector<std::wstring> relayClientWstrings; relayClientWstrings.reserve(relayClientsLen); for(int i = 0; i < relayClientsLen; i++) { @@ -278,6 +287,7 @@ WinFw_ApplyPolicyConnecting( return g_fwContext->applyPolicyConnecting( *settings, *relay, + exitIpAddr, relayClientWstrings, tunnelInterfaceAlias != nullptr ? std::make_optional(tunnelInterfaceAlias) : std::nullopt, MakeOptional(allowedEndpoint), @@ -309,6 +319,7 @@ WINFW_API WinFw_ApplyPolicyConnected( const WinFwSettings *settings, const WinFwEndpoint *relay, + const wchar_t *exitEndpointIp, const wchar_t **relayClients, size_t relayClientsLen, const wchar_t *tunnelInterfaceAlias, @@ -350,6 +361,14 @@ WinFw_ApplyPolicyConnected( THROW_ERROR("Invalid argument: nonTunnelDnsServers"); } + const auto exitIpAddr = (exitEndpointIp != nullptr) ? std::make_optional(wfp::IpAddress(exitEndpointIp)) : std::nullopt; + const auto entryIpAddr = wfp::IpAddress(relay->ip); + + if (entryIpAddr == exitIpAddr) + { + THROW_ERROR("Invalid argument: relay IP must not equal exitEndpointIp"); + } + std::vector<wfp::IpAddress> convertedTunnelDnsServers; std::vector<wfp::IpAddress> convertedNonTunnelDnsServers; @@ -398,6 +417,7 @@ WinFw_ApplyPolicyConnected( return g_fwContext->applyPolicyConnected( *settings, *relay, + exitIpAddr, relayClientWstrings, tunnelInterfaceAlias, convertedTunnelDnsServers, diff --git a/windows/winfw/src/winfw/winfw.h b/windows/winfw/src/winfw/winfw.h index f40d835a95..f3e26ea4aa 100644 --- a/windows/winfw/src/winfw/winfw.h +++ b/windows/winfw/src/winfw/winfw.h @@ -71,6 +71,7 @@ typedef struct tag_WinFwAllowedTunnelTraffic } WinFwAllowedTunnelTraffic; + /////////////////////////////////////////////////////////////////////////////// // Functions /////////////////////////////////////////////////////////////////////////////// @@ -161,6 +162,12 @@ enum WINFW_POLICY_STATUS // - Communication with the relay server // - Specified in-tunnel traffic, except DNS. // +// Parameters: +// +// exitEndpointIp: +// IP address of the exit relay, if it differs from `relay`. Otherwise, this should be set to +// `nullptr`. +// extern "C" WINFW_LINKAGE WINFW_POLICY_STATUS @@ -168,6 +175,7 @@ WINFW_API WinFw_ApplyPolicyConnecting( const WinFwSettings *settings, const WinFwEndpoint *relay, + const wchar_t *exitEndpointIp, const wchar_t **relayClient, size_t relayClientLen, const wchar_t *tunnelInterfaceAlias, @@ -190,6 +198,10 @@ WinFw_ApplyPolicyConnecting( // tunnelInterfaceAlias: // Friendly name of VPN tunnel interface // +// exitEndpointIp: +// IP address of the exit relay, if it differs from `relay`. Otherwise, this should be set to +// `nullptr`. +// extern "C" WINFW_LINKAGE WINFW_POLICY_STATUS @@ -197,6 +209,7 @@ WINFW_API WinFw_ApplyPolicyConnected( const WinFwSettings *settings, const WinFwEndpoint *relay, + const wchar_t *exitEndpointIp, const wchar_t **relayClient, size_t relayClientLen, const wchar_t *tunnelInterfaceAlias, |
