diff options
| author | Emīls Piņķis <emils@mullvad.net> | 2022-09-13 14:51:20 +0200 |
|---|---|---|
| committer | Emīls Piņķis <emils@mullvad.net> | 2022-09-13 14:51:20 +0200 |
| commit | d0a830effe2b17bad73fc43541eac4c600cbace1 (patch) | |
| tree | 783bbea4c1a93308d913bbe6c7e3c53acf8338fe | |
| parent | c5d2534ba7724c2d254f98c295b17a2d91a08f55 (diff) | |
| parent | 94250f3444422ce2d68aa10c58f77a9d9459a5c4 (diff) | |
| download | mullvadvpn-d0a830effe2b17bad73fc43541eac4c600cbace1.tar.xz mullvadvpn-d0a830effe2b17bad73fc43541eac4c600cbace1.zip | |
Merge branch 'linux-start-daemon-faster'
| -rw-r--r-- | CHANGELOG.md | 11 | ||||
| -rw-r--r-- | dist-assets/linux/after-install.sh | 3 | ||||
| -rw-r--r-- | dist-assets/linux/after-remove.sh | 2 | ||||
| -rw-r--r-- | dist-assets/linux/before-install.sh | 1 | ||||
| -rw-r--r-- | dist-assets/linux/before-remove.sh | 3 | ||||
| -rw-r--r-- | dist-assets/linux/mullvad-daemon.service | 12 | ||||
| -rw-r--r-- | dist-assets/linux/mullvad-early-boot-blocking.service | 16 | ||||
| -rw-r--r-- | dist-assets/linux/post-transaction.sh | 11 | ||||
| -rw-r--r-- | gui/tasks/distribution.js | 12 | ||||
| -rw-r--r-- | mullvad-daemon/src/cli.rs | 15 | ||||
| -rw-r--r-- | mullvad-daemon/src/early_boot_firewall.rs | 38 | ||||
| -rw-r--r-- | mullvad-daemon/src/main.rs | 29 | ||||
| -rw-r--r-- | talpid-core/src/firewall/linux.rs | 4 | ||||
| -rw-r--r-- | talpid-core/src/firewall/macos.rs | 4 | ||||
| -rw-r--r-- | talpid-core/src/firewall/mod.rs | 9 | ||||
| -rw-r--r-- | talpid-core/src/firewall/windows.rs | 20 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnected_state.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/error_state.rs | 2 |
18 files changed, 166 insertions, 28 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index e64bed0606..853af3f5aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,11 @@ Line wrap the file at 100 chars. Th - Upgrade Wintun to 0.14.1. +#### Linux +- The daemon binary and systemd unit file will now be placed in `/usr/bin/` and + `/usr/lib/systemd/system` respectively, to aid with starting the system service on systems where + `/opt` isn't mounted during early boot. + ### Fixed - Connect to TCP endpoints over IPv6 if IPv6 is enabled for WireGuard. - Fix udp2tcp not working when quantum-resistant tunnels are enabled. @@ -68,6 +73,12 @@ Line wrap the file at 100 chars. Th it was successful. - Don't fail install if the device tree contains nameless callout driver devices. +### Security +#### Linux +- Added traffic blocking during early boot, before the daemon starts, to prevent leaks in the case + that the system service starts after a networking daemon has already configured a network + interface. + ## [android/2022.2-beta2] - 2022-09-09 ### Changed diff --git a/dist-assets/linux/after-install.sh b/dist-assets/linux/after-install.sh index 632a3b6268..4a1771772f 100644 --- a/dist-assets/linux/after-install.sh +++ b/dist-assets/linux/after-install.sh @@ -3,5 +3,6 @@ set -eu chmod u+s "/usr/bin/mullvad-exclude" -systemctl enable "/opt/Mullvad VPN/resources/mullvad-daemon.service" +systemctl enable "/usr/lib/systemd/system/mullvad-daemon.service" systemctl start mullvad-daemon.service +systemctl enable "/usr/lib/systemd/system/mullvad-early-boot-blocking.service" diff --git a/dist-assets/linux/after-remove.sh b/dist-assets/linux/after-remove.sh index 8dfe875a46..22c9529982 100644 --- a/dist-assets/linux/after-remove.sh +++ b/dist-assets/linux/after-remove.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash set -eu +echo "Running after-remove.sh" + function remove_logs_and_cache { rm -r --interactive=never /var/log/mullvad-vpn/ || \ echo "Failed to remove mullvad-vpn logs" diff --git a/dist-assets/linux/before-install.sh b/dist-assets/linux/before-install.sh index a8efb02adb..5150f153ff 100644 --- a/dist-assets/linux/before-install.sh +++ b/dist-assets/linux/before-install.sh @@ -6,6 +6,7 @@ if which systemctl &> /dev/null; then /opt/Mullvad\ VPN/resources/mullvad-setup prepare-restart || true systemctl stop mullvad-daemon.service systemctl disable mullvad-daemon.service + systemctl disable mullvad-early-boot-blocking.service || true cp /var/log/mullvad-vpn/daemon.log /var/log/mullvad-vpn/old-install-daemon.log \ || echo "Failed to copy old daemon log" fi diff --git a/dist-assets/linux/before-remove.sh b/dist-assets/linux/before-remove.sh index 6ffb0669c8..bfa1faae9f 100644 --- a/dist-assets/linux/before-remove.sh +++ b/dist-assets/linux/before-remove.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash set -eu +echo "Running before-remove.sh" # SIGTERM for some reason causes the app to crash sometimes and SIGINT works as expected. pkill -2 -x "mullvad-gui" || true sleep 0.5 @@ -22,6 +23,8 @@ fi # the user might've disabled or stopped the service themselves already systemctl stop mullvad-daemon.service || true systemctl disable mullvad-daemon.service || true +systemctl stop mullvad-early-boot-blocking.service || true +systemctl disable mullvad-early-boot-blocking.service || true /opt/Mullvad\ VPN/resources/mullvad-setup reset-firewall || echo "Failed to reset firewall" /opt/Mullvad\ VPN/resources/mullvad-setup remove-device || echo "Failed to remove device from account" diff --git a/dist-assets/linux/mullvad-daemon.service b/dist-assets/linux/mullvad-daemon.service index f8d422df11..e36bd90eff 100644 --- a/dist-assets/linux/mullvad-daemon.service +++ b/dist-assets/linux/mullvad-daemon.service @@ -1,18 +1,20 @@ # Systemd service unit file for the Mullvad VPN daemon +# testing if new changes are added [Unit] Description=Mullvad VPN daemon -Wants=network.target -After=network-online.target -After=NetworkManager.service -After=systemd-resolved.service +Before=network-online.target +After=mullvad-early-boot-blocking.service NetworkManager.service systemd-resolved.service + StartLimitBurst=5 StartLimitIntervalSec=20 +RequiresMountsFor=/opt/Mullvad\x20VPN/resources/ [Service] Restart=always RestartSec=1 -ExecStart=/opt/Mullvad\x20VPN/resources/mullvad-daemon -v --disable-stdout-timestamps +ExecStart=/usr/bin/mullvad-daemon -v --disable-stdout-timestamps +Environment="MULLVAD_RESOURCE_DIR=/opt/Mullvad VPN/resources/" [Install] WantedBy=multi-user.target diff --git a/dist-assets/linux/mullvad-early-boot-blocking.service b/dist-assets/linux/mullvad-early-boot-blocking.service new file mode 100644 index 0000000000..466eebb796 --- /dev/null +++ b/dist-assets/linux/mullvad-early-boot-blocking.service @@ -0,0 +1,16 @@ +# Systemd service unit file to block all traffic during early boot. +# This is required since almost no distributions use a `network-pre.target`, +# which implies it's difficult to ensure that the daemon will start and block +# traffic before any network configuration will be applied. +# +[Unit] +Description=Mullvad early boot network blocker +DefaultDependencies=no +Before=basic.target mullvad-daemon.service + +[Service] +Type=oneshot +ExecStart=/usr/bin/mullvad-daemon --initialize-early-boot-firewall + +[Install] +WantedBy=mullvad-daemon.service diff --git a/dist-assets/linux/post-transaction.sh b/dist-assets/linux/post-transaction.sh index 78c5115f6f..a2a14d17d8 100644 --- a/dist-assets/linux/post-transaction.sh +++ b/dist-assets/linux/post-transaction.sh @@ -2,5 +2,12 @@ # This is to mitigate post-uninstall hooks being ran AFTER post-install hooks # during an upgrade on Fedora. set -eu -systemctl enable "/opt/Mullvad VPN/resources/mullvad-daemon.service" || true -systemctl start mullvad-daemon.service || true + +# Repeated enablement of the daemon service will result in the early-boot unit +# being executed when the daemon is already running, which results in the +# firewall rules being applied. +if ! systemctl is-enabled mullvad-daemon; then + systemctl enable "/usr/lib/systemd/system/mullvad-daemon.service" || true + systemctl start mullvad-daemon.service || true + systemctl enable "/usr/lib/systemd/system/mullvad-early-boot-blocking.service" || true +fi diff --git a/gui/tasks/distribution.js b/gui/tasks/distribution.js index 75e0f246cc..7152eb4b8a 100644 --- a/gui/tasks/distribution.js +++ b/gui/tasks/distribution.js @@ -171,11 +171,9 @@ const config = { extraFiles: [{ from: distAssets('linux/mullvad-gui-launcher.sh'), to: '.' }], extraResources: [ { from: distAssets(path.join(getLinuxTargetSubdir(), 'mullvad-problem-report')), to: '.' }, - { from: distAssets(path.join(getLinuxTargetSubdir(), 'mullvad-daemon')), to: '.' }, { from: distAssets(path.join(getLinuxTargetSubdir(), 'mullvad-setup')), to: '.' }, { from: distAssets(path.join(getLinuxTargetSubdir(), 'libtalpid_openvpn_plugin.so')), to: '.' }, { from: distAssets(path.join('binaries', '${env.TARGET_TRIPLE}', 'openvpn')), to: '.' }, - { from: distAssets('linux/mullvad-daemon.service'), to: '.' }, ], }, @@ -188,9 +186,10 @@ const config = { distAssets('linux/before-install.sh'), '--before-remove', distAssets('linux/before-remove.sh'), - '--config-files', - '/opt/Mullvad VPN/resources/mullvad-daemon.service', + distAssets('linux/mullvad-daemon.service') +'=/usr/lib/systemd/system/mullvad-daemon.service', + distAssets('linux/mullvad-early-boot-blocking.service') +'=/usr/lib/systemd/system/mullvad-early-boot-blocking.service', distAssets(path.join(getLinuxTargetSubdir(), 'mullvad')) + '=/usr/bin/', + distAssets(path.join(getLinuxTargetSubdir(), 'mullvad-daemon')) + '=/usr/bin/', distAssets(path.join(getLinuxTargetSubdir(), 'mullvad-exclude')) + '=/usr/bin/', distAssets('linux/problem-report-link') + '=/usr/bin/mullvad-problem-report', distAssets('shell-completions/mullvad.bash') + @@ -211,9 +210,10 @@ const config = { distAssets('linux/before-remove.sh'), '--rpm-posttrans', distAssets('linux/post-transaction.sh'), - '--config-files', - '/opt/Mullvad VPN/resources/mullvad-daemon.service', + distAssets('linux/mullvad-daemon.service') +'=/usr/lib/systemd/system/mullvad-daemon.service', + distAssets('linux/mullvad-early-boot-blocking.service') +'=/usr/lib/systemd/system/mullvad-early-boot-blocking.service', distAssets(path.join(getLinuxTargetSubdir(), 'mullvad')) + '=/usr/bin/', + distAssets(path.join(getLinuxTargetSubdir(), 'mullvad-daemon')) + '=/usr/bin/', distAssets(path.join(getLinuxTargetSubdir(), 'mullvad-exclude')) + '=/usr/bin/', distAssets('linux/problem-report-link') + '=/usr/bin/mullvad-problem-report', distAssets('shell-completions/mullvad.bash') + diff --git a/mullvad-daemon/src/cli.rs b/mullvad-daemon/src/cli.rs index 578bf74dff..e7e332c942 100644 --- a/mullvad-daemon/src/cli.rs +++ b/mullvad-daemon/src/cli.rs @@ -10,6 +10,8 @@ pub struct Config { pub run_as_service: bool, pub register_service: bool, pub restart_service: bool, + #[cfg(target_os = "linux")] + pub initialize_firewall_and_exit: bool, } pub fn get_config() -> &'static Config { @@ -31,11 +33,16 @@ pub fn create_config() -> Config { let log_to_file = !matches.is_present("disable_log_to_file"); let log_stdout_timestamps = !matches.is_present("disable_stdout_timestamps"); + #[cfg(target_os = "linux")] + let initialize_firewall_and_exit = + cfg!(target_os = "linux") && matches.is_present("initialize-early-boot-firewall"); let run_as_service = cfg!(windows) && matches.is_present("run_as_service"); let register_service = cfg!(windows) && matches.is_present("register_service"); let restart_service = cfg!(windows) && matches.is_present("restart_service"); Config { + #[cfg(target_os = "linux")] + initialize_firewall_and_exit, log_level, log_to_file, log_stdout_timestamps, @@ -106,5 +113,13 @@ fn create_app() -> App<'static> { .help("Restarts the existing system service"), ) } + + if cfg!(target_os = "linux") { + app = app.arg( + Arg::new("initialize-early-boot-firewall") + .long("initialize-early-boot-firewall") + .help("Initialize firewall to be used during early boot and exit"), + ) + } app } diff --git a/mullvad-daemon/src/early_boot_firewall.rs b/mullvad-daemon/src/early_boot_firewall.rs new file mode 100644 index 0000000000..7e93f47b27 --- /dev/null +++ b/mullvad-daemon/src/early_boot_firewall.rs @@ -0,0 +1,38 @@ +use mullvad_daemon::settings::{self, SettingsPersister}; +use talpid_core::firewall::{self, Firewall, FirewallPolicy}; + +#[derive(err_derive::Error, Debug)] +pub enum Error { + #[error(display = "Failed to initialize firewall")] + Firewall(#[error(source)] firewall::Error), + + #[error(display = "Failed to get settings path")] + Path(#[error(source)] mullvad_paths::Error), + + #[error(display = "Failed to get settings")] + Settings(#[error(source)] settings::Error), +} + +pub async fn initialize_firewall() -> Result<(), Error> { + let mut firewall = Firewall::new()?; + let allow_lan = get_allow_lan().await.unwrap_or_else(|err| { + log::info!( + "Not allowing LAN traffic due to failing to read settings: {}", + err + ); + false + }); + let policy = FirewallPolicy::Blocked { + allow_lan, + allowed_endpoint: None, + }; + log::info!("Applying firewall policy {policy}"); + firewall.apply_policy(policy)?; + Ok(()) +} + +async fn get_allow_lan() -> Result<bool, Error> { + let path = mullvad_paths::settings_dir()?; + let settings = SettingsPersister::load(&path).await; + Ok(settings.allow_lan) +} diff --git a/mullvad-daemon/src/main.rs b/mullvad-daemon/src/main.rs index f11bb30d98..13b4d8a268 100644 --- a/mullvad-daemon/src/main.rs +++ b/mullvad-daemon/src/main.rs @@ -11,12 +11,16 @@ use std::{path::PathBuf, thread, time::Duration}; use talpid_types::ErrorExt; mod cli; +#[cfg(target_os = "linux")] +mod early_boot_firewall; mod exception_logging; mod shutdown; #[cfg(windows)] mod system_service; const DAEMON_LOG_FILENAME: &str = "daemon.log"; +#[cfg(target_os = "linux")] +const EARLY_BOOT_LOG_FILENAME: &str = "early-boot-fw.log"; fn main() { let config = cli::get_config(); @@ -45,7 +49,18 @@ fn main() { fn init_logging(config: &cli::Config) -> Result<Option<PathBuf>, String> { let log_dir = get_log_dir(config)?; - let log_file = log_dir.as_ref().map(|dir| dir.join(DAEMON_LOG_FILENAME)); + + #[cfg(not(target_os = "linux"))] + let log_file_name = DAEMON_LOG_FILENAME; + + #[cfg(target_os = "linux")] + let log_file_name = if config.initialize_firewall_and_exit { + EARLY_BOOT_LOG_FILENAME + } else { + DAEMON_LOG_FILENAME + }; + + let log_file = log_dir.as_ref().map(|dir| dir.join(log_file_name)); logging::init_logger( config.log_level, @@ -95,7 +110,17 @@ async fn run_platform(config: &cli::Config, log_dir: Option<PathBuf>) -> Result< } } -#[cfg(not(windows))] +#[cfg(target_os = "linux")] +async fn run_platform(config: &cli::Config, log_dir: Option<PathBuf>) -> Result<(), String> { + if config.initialize_firewall_and_exit { + return crate::early_boot_firewall::initialize_firewall() + .await + .map_err(|err| format!("{}", err)); + } + run_standalone(log_dir).await +} + +#[cfg(not(any(windows, target_os = "linux")))] async fn run_platform(_config: &cli::Config, log_dir: Option<PathBuf>) -> Result<(), String> { run_standalone(log_dir).await } diff --git a/talpid-core/src/firewall/linux.rs b/talpid-core/src/firewall/linux.rs index cf4fb267dc..b873d71b62 100644 --- a/talpid-core/src/firewall/linux.rs +++ b/talpid-core/src/firewall/linux.rs @@ -605,7 +605,9 @@ impl<'a> PolicyBatch<'a> { allow_lan, allowed_endpoint, } => { - self.add_allow_endpoint_rules(&allowed_endpoint.endpoint); + if let Some(endpoint) = allowed_endpoint { + self.add_allow_endpoint_rules(&endpoint.endpoint); + } // Important to drop DNS before allowing LAN (to stop DNS leaking to the LAN) self.add_drop_dns_rule(); diff --git a/talpid-core/src/firewall/macos.rs b/talpid-core/src/firewall/macos.rs index 43b8e81bc1..6f59020990 100644 --- a/talpid-core/src/firewall/macos.rs +++ b/talpid-core/src/firewall/macos.rs @@ -178,7 +178,9 @@ impl Firewall { .. } => { let mut rules = Vec::new(); - rules.push(self.get_allowed_endpoint_rule(allowed_endpoint.endpoint)?); + if let Some(allowed_endpoint) = allowed_endpoint { + rules.push(self.get_allowed_endpoint_rule(allowed_endpoint.endpoint)?); + } if *allow_lan { // Important to block DNS before allow LAN (so DNS does not leak to the LAN) diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs index 599fa0b6ba..1f99fc2692 100644 --- a/talpid-core/src/firewall/mod.rs +++ b/talpid-core/src/firewall/mod.rs @@ -137,7 +137,7 @@ pub enum FirewallPolicy { /// Flag setting if communication with LAN networks should be possible. allow_lan: bool, /// Host that should be reachable while in the blocked state. - allowed_endpoint: AllowedEndpoint, + allowed_endpoint: Option<AllowedEndpoint>, /// Desination port for DNS traffic redirection. Traffic destined to `127.0.0.1:53` will be /// redirected to `127.0.0.1:$dns_redirect_port`. #[cfg(target_os = "macos")] @@ -210,9 +210,12 @@ impl fmt::Display for FirewallPolicy { .. } => write!( f, - "Blocked. {} LAN. Allowing endpoint {}", + "Blocked. {} LAN. Allowing endpoint: {}", if *allow_lan { "Allowing" } else { "Blocking" }, - allowed_endpoint, + allowed_endpoint + .as_ref() + .map(|endpoint| -> &dyn std::fmt::Display { endpoint }) + .unwrap_or(&"none"), ), } } diff --git a/talpid-core/src/firewall/windows.rs b/talpid-core/src/firewall/windows.rs index db0060f25d..23be309a63 100644 --- a/talpid-core/src/firewall/windows.rs +++ b/talpid-core/src/firewall/windows.rs @@ -128,7 +128,7 @@ impl Firewall { let cfg = &WinFwSettings::new(allow_lan); self.set_blocked_state( &cfg, - &WinFwAllowedEndpointContainer::from(allowed_endpoint).as_endpoint(), + allowed_endpoint.map(|endpoint| WinFwAllowedEndpointContainer::from(endpoint)), ) } } @@ -255,13 +255,23 @@ impl Firewall { fn set_blocked_state( &mut self, winfw_settings: &WinFwSettings, - allowed_endpoint: &WinFwAllowedEndpoint<'_>, + allowed_endpoint: Option<WinFwAllowedEndpointContainer>, ) -> Result<(), Error> { log::trace!("Applying 'blocked' firewall policy"); + let endpoint = allowed_endpoint + .as_ref() + .map(WinFwAllowedEndpointContainer::as_endpoint); + unsafe { - WinFw_ApplyPolicyBlocked(winfw_settings, allowed_endpoint) - .into_result() - .map_err(Error::ApplyingBlockedPolicy) + WinFw_ApplyPolicyBlocked( + winfw_settings, + endpoint + .as_ref() + .map(|container| container as *const _) + .unwrap_or(ptr::null()), + ) + .into_result() + .map_err(Error::ApplyingBlockedPolicy) } } } diff --git a/talpid-core/src/tunnel_state_machine/disconnected_state.rs b/talpid-core/src/tunnel_state_machine/disconnected_state.rs index 7b54384780..b6b52bbc45 100644 --- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs @@ -23,7 +23,7 @@ impl DisconnectedState { let result = if shared_values.block_when_disconnected { let policy = FirewallPolicy::Blocked { allow_lan: shared_values.allow_lan, - allowed_endpoint: shared_values.allowed_endpoint.clone(), + allowed_endpoint: Some(shared_values.allowed_endpoint.clone()), #[cfg(target_os = "macos")] dns_redirect_port: shared_values.filtering_resolver.listening_port(), }; diff --git a/talpid-core/src/tunnel_state_machine/error_state.rs b/talpid-core/src/tunnel_state_machine/error_state.rs index b12beecff4..7fe95c9f67 100644 --- a/talpid-core/src/tunnel_state_machine/error_state.rs +++ b/talpid-core/src/tunnel_state_machine/error_state.rs @@ -22,7 +22,7 @@ impl ErrorState { ) -> Result<(), FirewallPolicyError> { let policy = FirewallPolicy::Blocked { allow_lan: shared_values.allow_lan, - allowed_endpoint: shared_values.allowed_endpoint.clone(), + allowed_endpoint: Some(shared_values.allowed_endpoint.clone()), #[cfg(target_os = "macos")] dns_redirect_port: shared_values.filtering_resolver.listening_port(), }; |
