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 7, 2023
1 parent 44cda7d commit 7370407
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 61 deletions.
6 changes: 3 additions & 3 deletions mullvad-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use std::{
ops::Deref,
path::Path,
};
use talpid_types::{net::Endpoint, ErrorExt};
use talpid_types::{net::AllowedEndpoint, ErrorExt};

pub mod availability;
use availability::{ApiAvailability, ApiAvailabilityHandle};
Expand Down Expand Up @@ -221,13 +221,13 @@ pub enum Error {

/// Closure that receives the next API (real or proxy) endpoint to use for `api.mullvad.net`.
/// It should return a future that determines whether to reject the new endpoint or not.
pub trait ApiEndpointUpdateCallback: Fn(Endpoint) -> Self::AcceptedNewEndpoint {
pub trait ApiEndpointUpdateCallback: Fn(AllowedEndpoint) -> Self::AcceptedNewEndpoint {
type AcceptedNewEndpoint: Future<Output = bool> + Send;
}

impl<U, T: Future<Output = bool> + Send> ApiEndpointUpdateCallback for U
where
U: Fn(Endpoint) -> T,
U: Fn(AllowedEndpoint) -> T,
{
type AcceptedNewEndpoint = T;
}
Expand Down
32 changes: 31 additions & 1 deletion mullvad-api/src/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::{
task::{self, Poll},
};
use talpid_types::{
net::{Endpoint, TransportProtocol},
net::{AllowedClients, Endpoint, TransportProtocol},
ErrorExt,
};
use tokio::{
Expand Down Expand Up @@ -143,6 +143,36 @@ impl ApiConnectionMode {
}
}

#[cfg(unix)]
pub fn allowed_clients(&self) -> AllowedClients {
use access_method::Socks5;
match self {
ApiConnectionMode::Proxied(ProxyConfig::Socks(Socks5::Local(_))) => AllowedClients::All,
ApiConnectionMode::Direct | ApiConnectionMode::Proxied(_) => AllowedClients::Root,
}
}

#[cfg(windows)]
pub fn allowed_clients(&self) -> AllowedClients {
use access_method::Socks5;
match self {
ApiConnectionMode::Proxied(ProxyConfig::Socks(Socks5::Local(_))) => {
AllowedClients::all()
}
ApiConnectionMode::Direct | ApiConnectionMode::Proxied(_) => {
let daemon_exe = std::env::current_exe().expect("failed to obtain executable path");
vec![
daemon_exe
.parent()
.expect("missing executable parent directory")
.join("mullvad-problem-report.exe"),
daemon_exe,
]
.into()
}
}
}

pub fn is_proxy(&self) -> bool {
*self != ApiConnectionMode::Direct
}
Expand Down
6 changes: 4 additions & 2 deletions mullvad-api/src/rest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use std::{
time::Duration,
};
use talpid_types::{
net::{Endpoint, TransportProtocol},
net::{AllowedEndpoint, Endpoint, TransportProtocol},
ErrorExt,
};

Expand Down Expand Up @@ -217,8 +217,10 @@ impl<
TransportProtocol::Tcp,
),
};
let clients = new_config.allowed_clients();
let allowed_endpoint = AllowedEndpoint { endpoint, clients };
// Switch to new connection mode unless rejected by address change callback
if (self.new_address_callback)(endpoint).await {
if (self.new_address_callback)(allowed_endpoint).await {
self.connector_handle.set_connection_mode(new_config);
}
}
Expand Down
37 changes: 20 additions & 17 deletions mullvad-daemon/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ impl ApiEndpointUpdaterHandle {

pub fn callback(&self) -> impl ApiEndpointUpdateCallback {
let tunnel_tx = self.tunnel_cmd_tx.clone();
move |endpoint: Endpoint| {
move |allowed_endpoint: AllowedEndpoint| {
let inner_tx = tunnel_tx.clone();
async move {
let tunnel_tx = if let Some(tunnel_tx) = { inner_tx.lock().unwrap().as_ref() }
Expand All @@ -252,35 +252,38 @@ impl ApiEndpointUpdaterHandle {
};
let (result_tx, result_rx) = oneshot::channel();
let _ = tunnel_tx.unbounded_send(TunnelCommand::AllowEndpoint(
get_allowed_endpoint(endpoint),
allowed_endpoint.clone(),
result_tx,
));
// Wait for the firewall policy to be updated.
let _ = result_rx.await;
log::debug!("API endpoint: {endpoint}");
log::debug!(
"API endpoint: {endpoint}",
endpoint = allowed_endpoint.endpoint
);
true
}
}
}
}

pub(super) fn get_allowed_endpoint(endpoint: Endpoint) -> AllowedEndpoint {
#[cfg(unix)]
let clients = talpid_types::net::AllowedClients::Root;
#[cfg(windows)]
let daemon_exe = std::env::current_exe().expect("failed to obtain executable path");
#[cfg(windows)]
let clients = vec![
daemon_exe
.parent()
.expect("missing executable parent directory")
.join("mullvad-problem-report.exe"),
daemon_exe,
];
let clients = {
let daemon_exe = std::env::current_exe().expect("failed to obtain executable path");
vec![
daemon_exe
.parent()
.expect("missing executable parent directory")
.join("mullvad-problem-report.exe"),
daemon_exe,
]
.into()
};

AllowedEndpoint {
#[cfg(windows)]
clients,
endpoint,
}
AllowedEndpoint { endpoint, clients }
}

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
Loading

0 comments on commit 7370407

Please sign in to comment.