diff options
| author | David Lönnhager <david.l@mullvad.net> | 2025-05-14 15:48:17 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2025-05-19 16:20:27 +0200 |
| commit | 14c90ebff746d4be8e9aad3ad566f5dee34a777f (patch) | |
| tree | b8ce8cafe6669ea457939f06fc3e95e01d40106c | |
| parent | b7217f32415b398a2bf5b886c92db416e93b4b48 (diff) | |
| download | mullvadvpn-14c90ebff746d4be8e9aad3ad566f5dee34a777f.tar.xz mullvadvpn-14c90ebff746d4be8e9aad3ad566f5dee34a777f.zip | |
Delete mullvad directories if their permissions are unexpected
| -rw-r--r-- | mullvad-paths/src/cache.rs | 6 | ||||
| -rw-r--r-- | mullvad-paths/src/lib.rs | 82 | ||||
| -rw-r--r-- | mullvad-paths/src/logs.rs | 4 | ||||
| -rw-r--r-- | mullvad-paths/src/settings.rs | 2 |
4 files changed, 80 insertions, 14 deletions
diff --git a/mullvad-paths/src/cache.rs b/mullvad-paths/src/cache.rs index 9e96a89a91..801b1bfd3d 100644 --- a/mullvad-paths/src/cache.rs +++ b/mullvad-paths/src/cache.rs @@ -4,10 +4,10 @@ use std::{env, path::PathBuf}; /// Creates and returns the cache directory pointed to by `MULLVAD_CACHE_DIR`, or the default /// one if that variable is unset. pub fn cache_dir() -> Result<PathBuf> { - #[cfg(not(any(target_os = "macos", target_os = "windows")))] - let permissions = None; + #[cfg(target_os = "linux")] + let permissions = crate::Permissions::Any; #[cfg(target_os = "macos")] - let permissions = Some(std::os::unix::fs::PermissionsExt::from_mode(0o755)); + let permissions = crate::Permissions::ReadExecOnly; #[cfg(target_os = "windows")] let permissions = true; crate::create_and_return(get_cache_dir()?, permissions) diff --git a/mullvad-paths/src/lib.rs b/mullvad-paths/src/lib.rs index 4c16b7a437..40f9f5ae49 100644 --- a/mullvad-paths/src/lib.rs +++ b/mullvad-paths/src/lib.rs @@ -2,6 +2,8 @@ #[cfg(any(target_os = "linux", target_os = "macos"))] use std::fs; +#[cfg(any(target_os = "linux", target_os = "macos"))] +use std::path::Path; use std::{io, path::PathBuf}; #[cfg(windows)] @@ -14,6 +16,12 @@ pub enum Error { #[error("Failed to create directory {0}")] CreateDirFailed(String, #[source] io::Error), + #[error("Failed to remove directory {0}")] + RemoveDir(String, #[source] io::Error), + + #[error("Failed to get directory permissions on {0}")] + GetDirPermissionFailed(String, #[source] io::Error), + #[error("Failed to set directory permissions on {0}")] SetDirPermissionFailed(String, #[source] io::Error), @@ -39,6 +47,26 @@ const PRODUCT_NAME: &str = "mullvad-vpn"; #[cfg(windows)] pub const PRODUCT_NAME: &str = "Mullvad VPN"; +#[cfg(unix)] +#[derive(Clone, Copy, PartialEq)] +enum Permissions { + /// Do not set any particular permissions. They will be inherited instead. + Any, + /// Only root should have write access. Other users will have + /// read and execute permissions (0o755). + ReadExecOnly, +} + +#[cfg(unix)] +impl Permissions { + fn fs_permissions(self) -> Option<fs::Permissions> { + match self { + Permissions::Any => None, + Permissions::ReadExecOnly => Some(std::os::unix::fs::PermissionsExt::from_mode(0o755)), + } + } +} + #[cfg(windows)] fn get_allusersprofile_dir() -> Result<PathBuf> { match std::env::var_os("ALLUSERSPROFILE") { @@ -47,14 +75,54 @@ fn get_allusersprofile_dir() -> Result<PathBuf> { } } -#[cfg(any(target_os = "linux", target_os = "macos"))] -fn create_and_return(dir: PathBuf, permissions: Option<fs::Permissions>) -> Result<PathBuf> { - fs::create_dir_all(&dir).map_err(|e| Error::CreateDirFailed(dir.display().to_string(), e))?; - if let Some(permissions) = permissions { - fs::set_permissions(&dir, permissions) - .map_err(|e| Error::SetDirPermissionFailed(dir.display().to_string(), e))?; +#[cfg(unix)] +fn create_and_return(dir: PathBuf, permissions: Permissions) -> Result<PathBuf> { + use std::os::unix::fs::{DirBuilderExt, PermissionsExt}; + + let mut dir_builder = fs::DirBuilder::new(); + let fs_perms = permissions.fs_permissions(); + if let Some(fs_perms) = fs_perms.as_ref() { + dir_builder.mode(fs_perms.mode()); } - Ok(dir) + match dir_builder.create(&dir) { + Ok(()) => Ok(dir), + // The directory already exists + Err(error) if error.kind() == io::ErrorKind::AlreadyExists => { + // If the permissions are wrong, delete the directory + if !dir_is_root_owned(&dir, fs_perms.as_ref())? { + fs::remove_dir_all(&dir) + .or_else(|err| { + // ENOTDIR: If the path is not a directory, try to remove the file + if err.raw_os_error() == Some(20) { + fs::remove_file(&dir) + } else { + Err(err) + } + }) + .map_err(|e| Error::RemoveDir(dir.display().to_string(), e))?; + // Try to create it again + return create_and_return(dir, permissions); + } + // Correct permissions, so we're done + Ok(dir) + } + // Fail on any other error + Err(error) => Err(Error::CreateDirFailed(dir.display().to_string(), error)), + } +} + +#[cfg(unix)] +fn dir_is_root_owned(dir: &Path, perms: Option<&fs::Permissions>) -> Result<bool> { + use std::os::unix::fs::{MetadataExt, PermissionsExt}; + + const RELEVANT_BITS: u32 = 0o777; + + let meta = fs::symlink_metadata(&dir) + .map_err(|e| Error::GetDirPermissionFailed(dir.display().to_string(), e))?; + let matching_perms = perms + .map(|perms| (perms.mode() & RELEVANT_BITS) == (meta.permissions().mode() & RELEVANT_BITS)) + .unwrap_or(true); + Ok(matching_perms && meta.uid() == 0) } #[cfg(windows)] diff --git a/mullvad-paths/src/logs.rs b/mullvad-paths/src/logs.rs index fcc4926c75..eaed478794 100644 --- a/mullvad-paths/src/logs.rs +++ b/mullvad-paths/src/logs.rs @@ -6,9 +6,7 @@ use std::{env, path::PathBuf}; pub fn log_dir() -> Result<PathBuf> { #[cfg(unix)] { - use std::os::unix::fs::PermissionsExt; - let permissions = Some(PermissionsExt::from_mode(0o755)); - crate::create_and_return(get_log_dir()?, permissions) + crate::create_and_return(get_log_dir()?, crate::Permissions::ReadExecOnly) } #[cfg(target_os = "windows")] { diff --git a/mullvad-paths/src/settings.rs b/mullvad-paths/src/settings.rs index 97171dbb21..d4f154495e 100644 --- a/mullvad-paths/src/settings.rs +++ b/mullvad-paths/src/settings.rs @@ -6,7 +6,7 @@ use std::{env, path::PathBuf}; pub fn settings_dir() -> Result<PathBuf> { #[cfg(not(target_os = "windows"))] { - crate::create_and_return(get_settings_dir()?, None) + crate::create_and_return(get_settings_dir()?, crate::Permissions::Any) } #[cfg(target_os = "windows")] |
