summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2025-05-14 15:48:17 +0200
committerDavid Lönnhager <david.l@mullvad.net>2025-05-19 16:20:27 +0200
commit14c90ebff746d4be8e9aad3ad566f5dee34a777f (patch)
treeb8ce8cafe6669ea457939f06fc3e95e01d40106c
parentb7217f32415b398a2bf5b886c92db416e93b4b48 (diff)
downloadmullvadvpn-14c90ebff746d4be8e9aad3ad566f5dee34a777f.tar.xz
mullvadvpn-14c90ebff746d4be8e9aad3ad566f5dee34a777f.zip
Delete mullvad directories if their permissions are unexpected
-rw-r--r--mullvad-paths/src/cache.rs6
-rw-r--r--mullvad-paths/src/lib.rs82
-rw-r--r--mullvad-paths/src/logs.rs4
-rw-r--r--mullvad-paths/src/settings.rs2
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")]