Skip to content

Commit

Permalink
Configure firewall rules to allow proxy clients
Browse files Browse the repository at this point in the history
The default setting will (always) be to only allow processes with
root-privilege to send/receive traffic from an allowed endpoint.

This change is only supposed to be used with the local SOCKS5 api access
method.
  • Loading branch information
MarkusPettersson98 committed Nov 6, 2023
1 parent 44cda7d commit 8a6f8e5
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 31 deletions.
6 changes: 3 additions & 3 deletions mullvad-daemon/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
24 changes: 14 additions & 10 deletions talpid-core/src/firewall/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down
30 changes: 17 additions & 13 deletions talpid-core/src/firewall/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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)?);

// 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)
Expand Down Expand Up @@ -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)?);
}

if *allow_lan {
Expand Down Expand Up @@ -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<pfctl::FilterRule> {
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<Vec<pfctl::FilterRule>> {
Expand Down
90 changes: 85 additions & 5 deletions talpid-types/src/net/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,10 +276,32 @@ 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<PathBuf>,
/// How to connect to a certain `endpoint`.
pub endpoint: Endpoint,
/// Clients that should be allowed to communicate with `endpoint`.
pub clients: AllowedClients,
}

#[cfg(windows)]
impl AllowedEndpoint {
/// Create a new [`AllowedEndpoint`].
pub fn new<Clients: Into<AllowedClients>>(endpoint: Endpoint, clients: Clients) -> Self {
Self {
endpoint,
clients: clients.into(),
}
}
}

#[cfg(unix)]
impl AllowedEndpoint {
/// Create a new [`AllowedEndpoint`].
pub fn new(endpoint: Endpoint) -> Self {
Self {
endpoint,
clients: AllowedClients::default(),
}
}
}

impl fmt::Display for AllowedEndpoint {
Expand All @@ -289,8 +311,7 @@ impl fmt::Display for AllowedEndpoint {
#[cfg(windows)]
{
write!(f, "{} for", self.endpoint)?;
#[cfg(windows)]
for client in &self.clients {
for client in self.clients.iter() {
write!(
f,
" {}",
Expand All @@ -305,6 +326,65 @@ impl fmt::Display for AllowedEndpoint {
}
}

/// Clients which should be able to reach an allowed host in any tunnel state.
///
/// # Note
/// On Windows, there is no predetermined binary which should be allowed to leak
/// traffic outside of the tunnel. Thus, [`std::default::Default`] is not
/// implemented for [`AllowedClients`].
#[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<Vec<PathBuf>> for AllowedClients {
fn from(value: Vec<PathBuf>) -> Self {
Self(value.into())
}
}

#[cfg(windows)]
impl AllowedClients {
pub fn allow_all(&self) -> bool {
self.is_empty()
}
}

/// Clients which should be able to reach an allowed host in any tunnel state.
#[cfg(unix)]
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub enum AllowedClients {
/// Allow only clients running as `root` to leak traffic to an allowed [`Endpoint`].
///
/// # Note
/// The most secure client(s) is our own, which runs as root. Therefore,
/// [`AllowedClients::Root`] is allowed to be the default value.
#[default]
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(unix)]
impl AllowedClients {
pub fn allow_all(&self) -> bool {
matches!(self, AllowedClients::All)
}
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum AllowedTunnelTraffic {
None,
Expand Down

0 comments on commit 8a6f8e5

Please sign in to comment.