diff --git a/CHANGELOG.md b/CHANGELOG.md index 71e63f6084cd..b47012e17297 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,10 @@ Line wrap the file at 100 chars. Th ### Fixed - (macOS and Windows only) Add the correct route when using obfuscation with Wireguard. +#### macOS +- Fix daemon ending up in blocked state if the user toggled split tunneling without having granted + Full Disk Access to `mullvad-daemon`. This could only ever be accomplished from the CLI. + ## [2025.2] - 2025-01-08 ### Fixed diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx index df3c78d4b063..20b001f0959e 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx @@ -46,7 +46,7 @@ interface IProps { } export default function NotificationArea(props: IProps) { - const { showFullDiskAccessSettings, reconnectTunnel } = useAppContext(); + const { showFullDiskAccessSettings } = useAppContext(); const account = useSelector((state: IReduxState) => state.account); const locale = useSelector((state: IReduxState) => state.userInterface.locale); @@ -80,8 +80,7 @@ export default function NotificationArea(props: IProps) { const disableSplitTunneling = useCallback(async () => { setIsModalOpen(false); await setSplitTunnelingState(false); - await reconnectTunnel(); - }, [reconnectTunnel, setSplitTunnelingState]); + }, [setSplitTunnelingState]); const notificationProviders: InAppNotificationProvider[] = [ new ConnectingNotificationProvider({ tunnelState }), diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index b313e274bc2c..aa6a21a56481 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -1021,7 +1021,7 @@ impl Daemon { TunnelStateTransition::Disconnected => TunnelState::Disconnected { location: None }, TunnelStateTransition::Connecting(endpoint) => { let feature_indicators = compute_feature_indicators( - &self.settings.to_settings(), + self.settings.settings(), &endpoint, self.parameters_generator.last_relay_was_overridden().await, ); @@ -1033,7 +1033,7 @@ impl Daemon { } TunnelStateTransition::Connected(endpoint) => { let feature_indicators = compute_feature_indicators( - &self.settings.to_settings(), + self.settings.settings(), &endpoint, self.parameters_generator.last_relay_was_overridden().await, ); @@ -1199,7 +1199,7 @@ impl Daemon { .active_features() .any(|f| matches!(&f, FeatureIndicator::ServerIpOverride)); let new_feature_indicators = - compute_feature_indicators(&self.settings.to_settings(), endpoint, ip_override); + compute_feature_indicators(self.settings.settings(), endpoint, ip_override); // Update and broadcast the new feature indicators if they have changed if *feature_indicators != new_feature_indicators { // Make sure to update the daemon's actual tunnel state. Otherwise, feature @@ -1539,11 +1539,36 @@ impl Daemon { tx: ResponseTx<(), Error>, ) { let save_result = match update { - ExcludedPathsUpdate::SetState(state) => self - .settings - .update(move |settings| settings.split_tunnel.enable_exclusions = state) - .await - .map_err(Error::SettingsError), + ExcludedPathsUpdate::SetState(state) => { + let split_tunnel_was_enabled = + self.settings.settings().split_tunnel.enable_exclusions; + let save_result = self + .settings + .update(move |settings| settings.split_tunnel.enable_exclusions = state) + .await + .map_err(Error::SettingsError); + // If the user enables split tunneling without also enabling Full Disk Access + // (FDA), the daemon will enter the error state. This is unlikely, since it should + // only be possible via the CLI or if the user manages to disable FDA after having + // successfully enabled split tunneling. In any case, We have observed users + // getting confused over being blocked in this case, and this we may want to + // reconnect after disabling split tunneling. + // + // Since FDA is an implementation detail of split tunneling, we don't actually have + // a way of getting this information at this point, so we fallback to issuing a + // reconnect if the user disables split tunneling while in the error state. This + // code can be removed if we ever remove our dependency on FDA. + if cfg!(target_os = "macos") { + let split_tunnel_will_be_disabled = !state; + if self.tunnel_state.is_in_error_state() + && split_tunnel_was_enabled + && split_tunnel_will_be_disabled + { + self.reconnect_tunnel(); + } + } + save_result + } ExcludedPathsUpdate::SetPaths(paths) => self .settings .update(move |settings| settings.split_tunnel.apps = paths) @@ -2508,7 +2533,7 @@ impl Daemon { { Ok(settings_changed) => { if settings_changed { - let settings = self.settings.to_settings(); + let settings = self.settings.settings(); let resolvers = dns::addresses_from_options(&settings.tunnel_options.dns_options); self.send_tunnel_command(TunnelCommand::Dns( diff --git a/mullvad-daemon/src/settings/mod.rs b/mullvad-daemon/src/settings/mod.rs index 33513d90daac..dcb7e56106c5 100644 --- a/mullvad-daemon/src/settings/mod.rs +++ b/mullvad-daemon/src/settings/mod.rs @@ -248,6 +248,10 @@ impl SettingsPersister { Ok(()) } + pub const fn settings(&self) -> &Settings { + &self.settings + } + pub fn to_settings(&self) -> Settings { self.settings.clone() }