diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index a6dd0dead66b..f4a5487123ca 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -276,11 +276,11 @@ pub(super) fn get_allowed_endpoint(endpoint: Endpoint) -> AllowedEndpoint { daemon_exe, ]; - AllowedEndpoint { + AllowedEndpoint::new( + endpoint, #[cfg(windows)] clients, - endpoint, - } + ) } pub(crate) fn forward_offline_state( diff --git a/talpid-core/src/firewall/linux.rs b/talpid-core/src/firewall/linux.rs index 1b74fb30bd80..44d3233b50ca 100644 --- a/talpid-core/src/firewall/linux.rs +++ b/talpid-core/src/firewall/linux.rs @@ -14,7 +14,7 @@ use std::{ fs, io, net::{IpAddr, Ipv4Addr}, }; -use talpid_types::net::{AllowedTunnelTraffic, Endpoint, TransportProtocol}; +use talpid_types::net::{AllowedEndpoint, AllowedTunnelTraffic, Endpoint, TransportProtocol}; /// Priority for rules that tag split tunneling packets. Equals NF_IP_PRI_MANGLE. const MANGLE_CHAIN_PRIORITY: i32 = libc::NF_IP_PRI_MANGLE; @@ -519,7 +519,7 @@ impl<'a> PolicyBatch<'a> { allowed_tunnel_traffic, } => { self.add_allow_tunnel_endpoint_rules(peer_endpoint, fwmark); - self.add_allow_endpoint_rules(&allowed_endpoint.endpoint); + self.add_allow_endpoint_rules(&allowed_endpoint); // 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) @@ -568,7 +568,7 @@ impl<'a> PolicyBatch<'a> { allowed_endpoint, } => { if let Some(endpoint) = allowed_endpoint { - self.add_allow_endpoint_rules(&endpoint.endpoint); + self.add_allow_endpoint_rules(&endpoint); } // Important to drop DNS before allowing LAN (to stop DNS leaking to the LAN) @@ -628,24 +628,28 @@ impl<'a> PolicyBatch<'a> { /// Adds firewall rules allow traffic to flow to the API. Allows the app to reach the API in /// blocked states. - fn add_allow_endpoint_rules(&mut self, endpoint: &Endpoint) { + fn add_allow_endpoint_rules(&mut self, endpoint: &AllowedEndpoint) { let mut in_rule = Rule::new(&self.in_chain); - check_endpoint(&mut in_rule, End::Src, endpoint); + check_endpoint(&mut in_rule, End::Src, &endpoint.endpoint); let allowed_states = nftnl::expr::ct::States::ESTABLISHED.bits(); in_rule.add_expr(&nft_expr!(ct state)); in_rule.add_expr(&nft_expr!(bitwise mask allowed_states, xor 0u32)); in_rule.add_expr(&nft_expr!(cmp != 0u32)); - in_rule.add_expr(&nft_expr!(meta skuid)); - in_rule.add_expr(&nft_expr!(cmp == super::ROOT_UID)); + if !endpoint.clients.allow_all() { + in_rule.add_expr(&nft_expr!(meta skuid)); + in_rule.add_expr(&nft_expr!(cmp == super::ROOT_UID)); + } add_verdict(&mut in_rule, &Verdict::Accept); 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 == super::ROOT_UID)); + check_endpoint(&mut out_rule, End::Dst, &endpoint.endpoint); + if !endpoint.clients.allow_all() { + out_rule.add_expr(&nft_expr!(meta skuid)); + out_rule.add_expr(&nft_expr!(cmp == super::ROOT_UID)); + } add_verdict(&mut out_rule, &Verdict::Accept); self.batch.add(&out_rule, nftnl::MsgType::Add); diff --git a/talpid-core/src/firewall/macos.rs b/talpid-core/src/firewall/macos.rs index 4f95309890ca..01dc495712af 100644 --- a/talpid-core/src/firewall/macos.rs +++ b/talpid-core/src/firewall/macos.rs @@ -6,7 +6,7 @@ use std::{ net::{IpAddr, Ipv4Addr}, }; use subslice::SubsliceExt; -use talpid_types::net::{self, AllowedTunnelTraffic}; +use talpid_types::net::{self, AllowedEndpoint, AllowedTunnelTraffic}; pub use pfctl::Error; @@ -122,7 +122,7 @@ impl Firewall { allowed_tunnel_traffic, } => { let mut rules = vec![self.get_allow_relay_rule(*peer_endpoint)?]; - rules.push(self.get_allowed_endpoint_rule(allowed_endpoint.endpoint)?); + rules.push(self.get_allowed_endpoint_rule(allowed_endpoint.clone())?); // 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) @@ -175,7 +175,7 @@ impl Firewall { } => { let mut rules = Vec::new(); if let Some(allowed_endpoint) = allowed_endpoint { - rules.push(self.get_allowed_endpoint_rule(allowed_endpoint.endpoint)?); + rules.push(self.get_allowed_endpoint_rule(allowed_endpoint.clone())?); } if *allow_lan { @@ -287,22 +287,26 @@ impl Firewall { .build() } - /// Produces a rule that allows traffic to flow to the API. Allows the app to reach the API in - /// blocked states. + /// Produces a rule that allows traffic to flow to the API. Allows the app (or other apps if configured) + /// to reach the API in blocked states. fn get_allowed_endpoint_rule( &self, - allowed_endpoint: net::Endpoint, + allowed_endpoint: AllowedEndpoint, ) -> Result { - let pfctl_proto = as_pfctl_proto(allowed_endpoint.protocol); + let pfctl_proto = as_pfctl_proto(allowed_endpoint.endpoint.protocol); - self.create_rule_builder(FilterRuleAction::Pass) - .direction(pfctl::Direction::Out) - .to(allowed_endpoint.address) + let mut rule = self.create_rule_builder(FilterRuleAction::Pass); + rule.direction(pfctl::Direction::Out) + .to(allowed_endpoint.endpoint.address) .proto(pfctl_proto) .keep_state(pfctl::StatePolicy::Keep) - .user(Uid::from(super::ROOT_UID)) - .quick(true) - .build() + .quick(true); + + if !allowed_endpoint.clients.allow_all() { + rule.user(Uid::from(super::ROOT_UID)).build()?; + } + + rule.build() } fn get_block_dns_rules(&self) -> Result> { diff --git a/talpid-types/src/net/mod.rs b/talpid-types/src/net/mod.rs index 6ff56175cc65..cafcd806fb98 100644 --- a/talpid-types/src/net/mod.rs +++ b/talpid-types/src/net/mod.rs @@ -276,12 +276,33 @@ impl fmt::Display for Endpoint { /// Host that should be reachable in any tunnel state. #[derive(Debug, Clone, Eq, PartialEq)] pub struct AllowedEndpoint { - /// Paths that should be allowed to communicate with `endpoint`. - #[cfg(windows)] - pub clients: Vec, + /// Clients that should be allowed to communicate with `endpoint`. + pub clients: AllowedClients, pub endpoint: Endpoint, } +#[cfg(windows)] +impl AllowedEndpoint { + /// Create a new [`AllowedEndpoint`]. + pub fn new(endpoint: Endpoint) -> Self { + Self { + endpoint, + clients: Vec::new(), + } + } +} + +#[cfg(unix)] +impl AllowedEndpoint { + /// Create a new [`AllowedEndpoint`]. + pub fn new(endpoint: Endpoint) -> Self { + Self { + endpoint, + clients: AllowedClients::default(), + } + } +} + impl fmt::Display for AllowedEndpoint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { #[cfg(not(windows))] @@ -290,7 +311,7 @@ impl fmt::Display for AllowedEndpoint { { write!(f, "{} for", self.endpoint)?; #[cfg(windows)] - for client in &self.clients { + for client in self.clients.iter() { write!( f, " {}", @@ -305,6 +326,64 @@ impl fmt::Display for AllowedEndpoint { } } +/// Clients which should be able to reach an allowed host in any tunnel state. +#[cfg(windows)] +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct AllowedClients(std::sync::Arc<[PathBuf]>); + +#[cfg(windows)] +impl std::ops::Deref for AllowedClients { + type Target = [PathBuf]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[cfg(windows)] +impl From> for AllowedClients { + fn from(value: Vec) -> Self { + Self(value.into()) + } +} + +/// Clients which should be able to reach an allowed host in any tunnel state. +#[cfg(unix)] +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum AllowedClients { + /// Allow only clients running as `root` to leak traffic to an allowed [`Endpoint`]. + Root, + /// Allow *all* clients to leak traffic to an allowed [`Endpoint`]. + /// + /// This is necessary on platforms which does not have proper support for + /// split-tunneling, but which wants to support running local SOCKS5-proxies. + /// See TODO(REFER TO LOCAL SOCKS5 PROXY SUPPORT DOCS) for further information. + All, +} + +#[cfg(windows)] +impl AllowedClients { + pub fn allow_all(&self) -> bool { + self.is_empty() + } +} + +#[cfg(unix)] +impl AllowedClients { + pub fn allow_all(&self) -> bool { + matches!(self, AllowedClients::All) + } +} + +impl Default for AllowedClients { + /// Returns [`AllowedClients::Root`]. + /// + /// The most secure client(s) is our own, which runs as root. + fn default() -> Self { + AllowedClients::Root + } +} + #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum AllowedTunnelTraffic { None,