diff options
| -rw-r--r-- | docs/security.md | 7 | ||||
| -rw-r--r-- | talpid-core/src/dns/linux/network_manager.rs | 7 | ||||
| -rw-r--r-- | talpid-core/src/firewall/linux.rs | 19 | ||||
| -rw-r--r-- | talpid-core/src/firewall/mod.rs | 8 | ||||
| -rw-r--r-- | talpid-core/src/linux.rs | 3 | ||||
| -rw-r--r-- | talpid-core/src/process/openvpn.rs | 7 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/wireguard/config.rs | 8 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 35 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connecting_state.rs | 21 | ||||
| -rw-r--r-- | talpid-types/src/net/mod.rs | 19 |
10 files changed, 88 insertions, 46 deletions
diff --git a/docs/security.md b/docs/security.md index 7fc5369bd3..786cf99d8b 100644 --- a/docs/security.md +++ b/docs/security.md @@ -124,8 +124,11 @@ VPN tunnel is allowed on all interfaces, together with responses to this outgoin First hop means the bridge server if one is used, otherwise the VPN server directly. This IP+port+protocol combination should only be allowed for the process establishing the VPN tunnel, or only administrator level processes, depending on what the platform firewall -allows restricting. On Windows the rule only allows processes from binaries in certain paths. -On Linux and macOS the rule only allows packets from processes running as `root`. +allows restricting. On Windows the rule only allows processes from binaries in certain paths. macOS +the rule only allows packets from processes running as `root`. On Linux, the rule only allows +packets that have the mark `0x6d6f6c65` set: setting a firewall mark on traffic requires elevated +privileges when using tunnels that support marking traffic, otherwise the rule is the same as on +macOS: the packet needs to originate from a process running as `root`. This process/user check is important to not allow unprivileged programs to leak packets to this IP outside the tunnel, as those packets can be fingerprinted. diff --git a/talpid-core/src/dns/linux/network_manager.rs b/talpid-core/src/dns/linux/network_manager.rs index 0e517e07f1..5a17c3c59e 100644 --- a/talpid-core/src/dns/linux/network_manager.rs +++ b/talpid-core/src/dns/linux/network_manager.rs @@ -248,6 +248,13 @@ impl NetworkManager { Self::update_dns_config(&mut settings, "ipv6", v6_dns); } + if let Some(wg_config) = settings.get_mut("wireguard") { + wg_config.insert( + "fwmark", + Variant(Box::new(crate::linux::TUNNEL_FW_MARK) as Box<dyn RefArg>), + ); + } + self.reapply_settings(&device, settings, version_id)?; self.device = Some(device); diff --git a/talpid-core/src/firewall/linux.rs b/talpid-core/src/firewall/linux.rs index 3a68d3734d..f4a3e07378 100644 --- a/talpid-core/src/firewall/linux.rs +++ b/talpid-core/src/firewall/linux.rs @@ -445,9 +445,11 @@ impl<'a> PolicyBatch<'a> { peer_endpoint, pingable_hosts, allow_lan, + use_fwmark, } => { self.add_allow_icmp_pingable_hosts(&pingable_hosts); - self.add_allow_endpoint_rules(peer_endpoint); + self.add_allow_endpoint_rules(peer_endpoint, *use_fwmark); + // Important to block DNS after allow relay rule (so the relay can operate // over port 53) but before allow LAN (so DNS does not leak to the LAN) self.add_drop_dns_rule(); @@ -457,8 +459,9 @@ impl<'a> PolicyBatch<'a> { peer_endpoint, tunnel, allow_lan, + use_fwmark, } => { - self.add_allow_endpoint_rules(peer_endpoint); + self.add_allow_endpoint_rules(peer_endpoint, *use_fwmark); self.add_allow_dns_rules(tunnel, TransportProtocol::Udp)?; self.add_allow_dns_rules(tunnel, TransportProtocol::Tcp)?; // Important to block DNS *before* we allow the tunnel and allow LAN. So DNS @@ -492,7 +495,7 @@ impl<'a> PolicyBatch<'a> { Ok(()) } - fn add_allow_endpoint_rules(&mut self, endpoint: &Endpoint) { + fn add_allow_endpoint_rules(&mut self, endpoint: &Endpoint, use_fwmark: bool) { let mut in_rule = Rule::new(&self.in_chain); check_endpoint(&mut in_rule, End::Src, endpoint); @@ -504,11 +507,15 @@ impl<'a> PolicyBatch<'a> { self.batch.add(&in_rule, nftnl::MsgType::Add); - let mut out_rule = Rule::new(&self.out_chain); check_endpoint(&mut out_rule, End::Dst, endpoint); - out_rule.add_expr(&nft_expr!(meta skuid)); - out_rule.add_expr(&nft_expr!(cmp == 0u32)); + if use_fwmark { + out_rule.add_expr(&nft_expr!(meta mark)); + out_rule.add_expr(&nft_expr!(cmp == crate::linux::TUNNEL_FW_MARK)); + } else { + out_rule.add_expr(&nft_expr!(meta skuid)); + out_rule.add_expr(&nft_expr!(cmp == 0u32)); + } add_verdict(&mut out_rule, &Verdict::Accept); self.batch.add(&out_rule, nftnl::MsgType::Add); diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs index 6b65dd5b70..4d8d3f0459 100644 --- a/talpid-core/src/firewall/mod.rs +++ b/talpid-core/src/firewall/mod.rs @@ -97,6 +97,10 @@ pub enum FirewallPolicy { /// A process that is allowed to send packets to the relay. #[cfg(windows)] relay_client: PathBuf, + /// Whether rule for allowing traffic to endpoint should match a firewall mark or match on + /// root UID. + #[cfg(target_os = "linux")] + use_fwmark: bool, }, /// Allow traffic only to server and over tunnel interface @@ -110,6 +114,10 @@ pub enum FirewallPolicy { /// A process that is allowed to send packets to the relay. #[cfg(windows)] relay_client: PathBuf, + /// Whether rule for allowing traffic to endpoint should match a firewall mark or match on + /// root UID. + #[cfg(target_os = "linux")] + use_fwmark: bool, }, /// Block all network traffic in and out from the computer. diff --git a/talpid-core/src/linux.rs b/talpid-core/src/linux.rs index 05655bf4be..47d0714813 100644 --- a/talpid-core/src/linux.rs +++ b/talpid-core/src/linux.rs @@ -25,3 +25,6 @@ pub enum IfaceIndexLookupError { #[error(display = "Failed to get index for interface {}", _0)] InterfaceLookupError(String, #[error(source)] io::Error), } + +// b"mole" is [ 0x6d, 0x6f 0x6c, 0x65 ] +pub const TUNNEL_FW_MARK: u32 = 0x6d6f6c65; diff --git a/talpid-core/src/process/openvpn.rs b/talpid-core/src/process/openvpn.rs index e07be4fa16..7922ba952c 100644 --- a/talpid-core/src/process/openvpn.rs +++ b/talpid-core/src/process/openvpn.rs @@ -249,6 +249,13 @@ impl OpenVpnCommand { args.extend(Self::tls_cipher_arguments().iter().map(OsString::from)); args.extend(self.proxy_arguments().iter().map(OsString::from)); + #[cfg(target_os = "linux")] + args.extend( + ["--mark", &crate::linux::TUNNEL_FW_MARK.to_string()] + .iter() + .map(OsString::from), + ); + args } diff --git a/talpid-core/src/tunnel/wireguard/config.rs b/talpid-core/src/tunnel/wireguard/config.rs index 4f0e851b1e..87c0d2c0f0 100644 --- a/talpid-core/src/tunnel/wireguard/config.rs +++ b/talpid-core/src/tunnel/wireguard/config.rs @@ -17,6 +17,9 @@ pub struct Config { pub ipv6_gateway: Option<Ipv6Addr>, /// Maximum transmission unit for the tunnel pub mtu: u16, + /// Firewall mark + #[cfg(target_os = "linux")] + pub fwmark: u32, } const DEFAULT_MTU: u16 = 1380; @@ -96,6 +99,8 @@ impl Config { ipv4_gateway: connection_config.ipv4_gateway, ipv6_gateway, mtu, + #[cfg(target_os = "linux")] + fwmark: crate::linux::TUNNEL_FW_MARK, }) } @@ -108,6 +113,9 @@ impl Config { .add("private_key", self.tunnel.private_key.to_bytes().as_ref()) .add("listen_port", "0"); + #[cfg(target_os = "linux")] + wg_conf.add("fwmark", self.fwmark.to_string().as_str()); + wg_conf.add("replace_peers", "true"); for peer in &self.peers { diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index 43595a4e79..dd259a87d9 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -11,7 +11,7 @@ use futures01::{ Async, Future, Stream, }; use talpid_types::{ - net::{Endpoint, TunnelParameters}, + net::TunnelParameters, tunnel::{ErrorStateCause, FirewallPolicyError}, BoxedError, ErrorExt, }; @@ -52,19 +52,7 @@ impl ConnectedState { &self, shared_values: &mut SharedTunnelStateValues, ) -> Result<(), FirewallPolicyError> { - // If a proxy is specified we need to pass it on as the peer endpoint. - let peer_endpoint = self.get_endpoint_from_params(); - - let policy = FirewallPolicy::Connected { - peer_endpoint, - tunnel: self.metadata.clone(), - allow_lan: shared_values.allow_lan, - #[cfg(windows)] - relay_client: TunnelMonitor::get_relay_client( - &shared_values.resource_dir, - &self.tunnel_parameters, - ), - }; + let policy = self.get_firewall_policy(shared_values); shared_values .firewall .apply_policy(policy) @@ -85,13 +73,18 @@ impl ConnectedState { }) } - fn get_endpoint_from_params(&self) -> Endpoint { - match self.tunnel_parameters { - TunnelParameters::OpenVpn(ref params) => match params.proxy { - Some(ref proxy_settings) => proxy_settings.get_endpoint().endpoint, - None => params.config.endpoint, - }, - TunnelParameters::Wireguard(ref params) => params.connection.get_endpoint(), + fn get_firewall_policy(&self, shared_values: &SharedTunnelStateValues) -> FirewallPolicy { + FirewallPolicy::Connected { + peer_endpoint: self.tunnel_parameters.get_next_hop_endpoint(), + tunnel: self.metadata.clone(), + allow_lan: shared_values.allow_lan, + #[cfg(windows)] + relay_client: TunnelMonitor::get_relay_client( + &shared_values.resource_dir, + &self.tunnel_parameters, + ), + #[cfg(target_os = "linux")] + use_fwmark: self.tunnel_parameters.get_proxy_endpoint().is_none(), } } diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index d206b34a23..859ef52eb0 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -22,7 +22,7 @@ use std::{ time::{Duration, Instant}, }; use talpid_types::{ - net::{openvpn, TunnelParameters}, + net::TunnelParameters, tunnel::{ErrorStateCause, FirewallPolicyError}, ErrorExt, }; @@ -48,13 +48,7 @@ impl ConnectingState { shared_values: &mut SharedTunnelStateValues, params: &TunnelParameters, ) -> Result<(), FirewallPolicyError> { - let proxy = &get_openvpn_proxy_settings(¶ms); - let endpoint = params.get_tunnel_endpoint().endpoint; - - let peer_endpoint = match proxy { - Some(proxy_settings) => proxy_settings.get_endpoint().endpoint, - None => endpoint, - }; + let peer_endpoint = params.get_next_hop_endpoint(); let policy = FirewallPolicy::Connecting { peer_endpoint, @@ -62,6 +56,8 @@ impl ConnectingState { allow_lan: shared_values.allow_lan, #[cfg(windows)] relay_client: TunnelMonitor::get_relay_client(&shared_values.resource_dir, ¶ms), + #[cfg(target_os = "linux")] + use_fwmark: params.get_proxy_endpoint().is_none(), }; shared_values .firewall @@ -312,15 +308,6 @@ impl ConnectingState { } } -fn get_openvpn_proxy_settings( - tunnel_parameters: &TunnelParameters, -) -> &Option<openvpn::ProxySettings> { - match tunnel_parameters { - TunnelParameters::OpenVpn(ref config) => &config.proxy, - _ => &None, - } -} - fn should_retry(error: &tunnel::Error) -> bool { #[cfg(not(windows))] use tunnel::wireguard::{Error, TunnelError}; diff --git a/talpid-types/src/net/mod.rs b/talpid-types/src/net/mod.rs index 97292fd381..15bf33a5bb 100644 --- a/talpid-types/src/net/mod.rs +++ b/talpid-types/src/net/mod.rs @@ -37,6 +37,25 @@ impl TunnelParameters { } } + // Returns the endpoint that will be connected to + pub fn get_next_hop_endpoint(&self) -> Endpoint { + match self { + TunnelParameters::OpenVpn(params) => params + .proxy + .as_ref() + .map(|proxy| proxy.get_endpoint().endpoint) + .unwrap_or(params.config.endpoint), + TunnelParameters::Wireguard(params) => params.connection.get_endpoint(), + } + } + + pub fn get_proxy_endpoint(&self) -> Option<openvpn::ProxySettings> { + match self { + TunnelParameters::OpenVpn(params) => params.proxy.clone(), + _ => None, + } + } + pub fn get_generic_options(&self) -> &GenericTunnelOptions { match &self { TunnelParameters::OpenVpn(params) => ¶ms.generic_options, |
