Skip to content

Restart mullvad-daemon after user grants Full Disk Access (macOS) #6381

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions gui/src/main/daemon-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,10 @@ export class DaemonRpc {
await this.callEmpty(this.client.updateDevice);
}

public async prepareRestart(quit: boolean) {
await this.callBool(this.client.prepareRestartV2, quit);
}

public async setDaitaSettings(daitaSettings: IDaitaSettings): Promise<void> {
const grpcDaitaSettings = new grpcTypes.DaitaSettings();
grpcDaitaSettings.setEnabled(daitaSettings.enabled);
Expand Down
6 changes: 5 additions & 1 deletion gui/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,11 @@ class ApplicationMain
}
};

private onBeforeQuit = (event: Electron.Event) => {
private onBeforeQuit = async (event: Electron.Event) => {
if (this.tunnelState.hasReceivedFullDiskAccessError) {
await this.daemonRpc.prepareRestart(true);
}

log.info('before-quit received');
if (this.quitInitiated) {
event.preventDefault();
Expand Down
13 changes: 12 additions & 1 deletion gui/src/main/tunnel-state.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { connectEnabled, disconnectEnabled, reconnectEnabled } from '../shared/connect-helper';
import { ILocation, TunnelState } from '../shared/daemon-rpc-types';
import { ErrorStateCause, ILocation, TunnelState } from '../shared/daemon-rpc-types';
import { Scheduler } from '../shared/scheduler';

export interface TunnelStateProvider {
Expand All @@ -20,10 +20,15 @@ export default class TunnelStateHandler {
// Scheduler for discarding the assumed next state.
private tunnelStateFallbackScheduler = new Scheduler();

private receivedFullDiskAccessError = false;

private lastKnownDisconnectedLocation: Partial<ILocation> | undefined;

public constructor(private delegate: TunnelStateHandlerDelegate) {}

public get hasReceivedFullDiskAccessError() {
return this.receivedFullDiskAccessError;
}
public get tunnelState() {
return this.tunnelStateValue;
}
Expand Down Expand Up @@ -53,6 +58,12 @@ export default class TunnelStateHandler {
}

public handleNewTunnelState(newState: TunnelState) {
if (newState.state === 'error' && newState.details) {
if (newState.details.cause === ErrorStateCause.needFullDiskPermissions) {
this.receivedFullDiskAccessError = true;
}
}

// If there's a fallback state set then the app is in an assumed next state and need to check
// if it's now reached or if the current state should be ignored and set as the fallback state.
if (this.tunnelStateFallback) {
Expand Down
15 changes: 15 additions & 0 deletions mullvad-cli/src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,21 @@ fn print_error_state(error_state: &ErrorState) {
println!("Blocked: {cause}");
println!("Your kernel might be terribly out of date or missing nftables");
}
#[cfg(target_os = "macos")]
cause @ talpid_types::tunnel::ErrorStateCause::NeedFullDiskPermissions => {
println!("Blocked: {cause}");
println!();
println!(
r#"Enable "Full Disk Access" for "Mullvad VPN" in the macOS system settings:"#
);
println!(
r#"open "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles""#
);
println!();
println!("Restart the Mullvad daemon for the change to take effect:");
println!("launchctl unload -w /Library/LaunchDaemons/net.mullvad.daemon.plist");
println!("launchctl load -w /Library/LaunchDaemons/net.mullvad.daemon.plist");
}
talpid_types::tunnel::ErrorStateCause::AuthFailed(Some(auth_failed)) => {
println!(
"Blocked: Authentication with remote server failed: {}",
Expand Down
17 changes: 13 additions & 4 deletions mullvad-daemon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ use std::{
sync::{Arc, Weak},
time::Duration,
};
use talpid_core::split_tunnel;
use talpid_core::{
mpsc::Sender,
split_tunnel,
tunnel_state_machine::{self, TunnelCommand, TunnelStateMachineHandle},
};
#[cfg(target_os = "android")]
Expand Down Expand Up @@ -350,7 +350,7 @@ pub enum DaemonCommand {
SetObfuscationSettings(ResponseTx<(), settings::Error>, ObfuscationSettings),
/// Saves the target tunnel state and enters a blocking state. The state is restored
/// upon restart.
PrepareRestart,
PrepareRestart(bool),
/// Causes a socket to bypass the tunnel. This has no effect when connected. It is only used
/// to bypass the tunnel in blocking states.
#[cfg(target_os = "android")]
Expand Down Expand Up @@ -1281,7 +1281,7 @@ where
SetObfuscationSettings(tx, settings) => {
self.on_set_obfuscation_settings(tx, settings).await
}
PrepareRestart => self.on_prepare_restart(),
PrepareRestart(shutdown) => self.on_prepare_restart(shutdown),
#[cfg(target_os = "android")]
BypassSocket(fd, tx) => self.on_bypass_socket(fd, tx),
#[cfg(target_os = "android")]
Expand Down Expand Up @@ -2688,7 +2688,11 @@ where
self.disconnect_tunnel();
}

fn on_prepare_restart(&mut self) {
/// Prepare the daemon for a restart by setting the target state to [`TargetState::Secured`].
///
/// - `shutdown`: If the daemon should shut down itself when after setting the secured target
/// state. set to `false` if the intention is to close the daemon process manually.
fn on_prepare_restart(&mut self, shutdown: bool) {
// TODO: See if this can be made to also shut down the daemon
// without causing the service to be restarted.

Expand All @@ -2697,6 +2701,11 @@ where
self.send_tunnel_command(TunnelCommand::BlockWhenDisconnected(true, tx));
}
self.target_state.lock();

if shutdown {
self.state.shutdown(&self.tunnel_state);
self.disconnect_tunnel();
}
}

#[cfg(target_os = "android")]
Expand Down
13 changes: 10 additions & 3 deletions mullvad-daemon/src/management_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ use mullvad_management_interface::{
types::{self, daemon_event, management_service_server::ManagementService},
Code, Request, Response, Status,
};
use mullvad_types::settings::DnsOptions;
use mullvad_types::{
account::AccountToken,
relay_constraints::{
BridgeSettings, BridgeState, ObfuscationSettings, RelayOverride, RelaySettings,
},
relay_list::RelayList,
settings::Settings,
settings::{DnsOptions, Settings},
states::{TargetState, TunnelState},
version,
wireguard::{RotationInterval, RotationIntervalError},
Expand Down Expand Up @@ -104,7 +103,15 @@ impl ManagementService for ManagementServiceImpl {

async fn prepare_restart(&self, _: Request<()>) -> ServiceResult<()> {
log::debug!("prepare_restart");
self.send_command_to_daemon(DaemonCommand::PrepareRestart)?;
// Note: The old `PrepareRestart` behavior never shutdown the daemon.
let shutdown = false;
self.send_command_to_daemon(DaemonCommand::PrepareRestart(shutdown))?;
Ok(Response::new(()))
}

async fn prepare_restart_v2(&self, shutdown: Request<bool>) -> ServiceResult<()> {
log::debug!("prepare_restart_v2");
self.send_command_to_daemon(DaemonCommand::PrepareRestart(shutdown.into_inner()))?;
Ok(Response::new(()))
}

Expand Down
4 changes: 4 additions & 0 deletions mullvad-management-interface/proto/management_interface.proto
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ service ManagementService {

// Control the daemon and receive events
rpc EventsListen(google.protobuf.Empty) returns (stream DaemonEvent) {}
// DEPRECATED: Prefer PrepareRestartV2.
rpc PrepareRestart(google.protobuf.Empty) returns (google.protobuf.Empty) {}
// Takes a a boolean argument which says whether the daemon should stop after
// it is done preparing for a restart.
rpc PrepareRestartV2(google.protobuf.BoolValue) returns (google.protobuf.Empty) {}
rpc FactoryReset(google.protobuf.Empty) returns (google.protobuf.Empty) {}

rpc GetCurrentVersion(google.protobuf.Empty) returns (google.protobuf.StringValue) {}
Expand Down
14 changes: 14 additions & 0 deletions mullvad-management-interface/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,25 @@ impl MullvadProxyClient {
}))
}

/// DEPRECATED: Prefer to use `prepare_restart_v2`.
pub async fn prepare_restart(&mut self) -> Result<()> {
self.0.prepare_restart(()).await.map_err(Error::Rpc)?;
Ok(())
}

/// Tell the daemon to get ready for a restart by securing a user, i.e. putting firewall rules
/// in place.
///
/// - `shutdown`: Whether the daemon should shutdown immediately after its prepare-for-restart
/// routine.
pub async fn prepare_restart_v2(&mut self, shutdown: bool) -> Result<()> {
self.0
.prepare_restart_v2(shutdown)
.await
.map_err(Error::Rpc)?;
Ok(())
}

pub async fn factory_reset(&mut self) -> Result<()> {
self.0.factory_reset(()).await.map_err(Error::Rpc)?;
Ok(())
Expand Down
Loading