summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorEmīls Piņķis <emils@mullvad.net>2022-09-13 14:51:20 +0200
committerEmīls Piņķis <emils@mullvad.net>2022-09-13 14:51:20 +0200
commitd0a830effe2b17bad73fc43541eac4c600cbace1 (patch)
tree783bbea4c1a93308d913bbe6c7e3c53acf8338fe
parentc5d2534ba7724c2d254f98c295b17a2d91a08f55 (diff)
parent94250f3444422ce2d68aa10c58f77a9d9459a5c4 (diff)
downloadmullvadvpn-d0a830effe2b17bad73fc43541eac4c600cbace1.tar.xz
mullvadvpn-d0a830effe2b17bad73fc43541eac4c600cbace1.zip
Merge branch 'linux-start-daemon-faster'
-rw-r--r--CHANGELOG.md11
-rw-r--r--dist-assets/linux/after-install.sh3
-rw-r--r--dist-assets/linux/after-remove.sh2
-rw-r--r--dist-assets/linux/before-install.sh1
-rw-r--r--dist-assets/linux/before-remove.sh3
-rw-r--r--dist-assets/linux/mullvad-daemon.service12
-rw-r--r--dist-assets/linux/mullvad-early-boot-blocking.service16
-rw-r--r--dist-assets/linux/post-transaction.sh11
-rw-r--r--gui/tasks/distribution.js12
-rw-r--r--mullvad-daemon/src/cli.rs15
-rw-r--r--mullvad-daemon/src/early_boot_firewall.rs38
-rw-r--r--mullvad-daemon/src/main.rs29
-rw-r--r--talpid-core/src/firewall/linux.rs4
-rw-r--r--talpid-core/src/firewall/macos.rs4
-rw-r--r--talpid-core/src/firewall/mod.rs9
-rw-r--r--talpid-core/src/firewall/windows.rs20
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnected_state.rs2
-rw-r--r--talpid-core/src/tunnel_state_machine/error_state.rs2
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(),
};