summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2025-05-16 11:56:17 +0200
committerDavid Lönnhager <david.l@mullvad.net>2025-05-19 16:20:27 +0200
commitd8c45d79a806969fcaa45d4ce7b73528a5b41d4b (patch)
tree02955d95dd9574633dfbf79f1b280b0bf1340daa
parente8ffdb8325e80de5b756deac8f9a307a1d4dd2b4 (diff)
downloadmullvadvpn-d8c45d79a806969fcaa45d4ce7b73528a5b41d4b.tar.xz
mullvadvpn-d8c45d79a806969fcaa45d4ce7b73528a5b41d4b.zip
Allow stricter-than-needed mode in mullvad-paths
-rw-r--r--mullvad-paths/src/unix.rs43
1 files changed, 35 insertions, 8 deletions
diff --git a/mullvad-paths/src/unix.rs b/mullvad-paths/src/unix.rs
index 7dd7112259..8f2e5572b1 100644
--- a/mullvad-paths/src/unix.rs
+++ b/mullvad-paths/src/unix.rs
@@ -36,12 +36,17 @@ pub fn create_and_return(dir: PathBuf, permissions: Permissions) -> Result<PathB
Ok(()) => Ok(dir),
// The directory already exists
Err(error) if error.kind() == io::ErrorKind::AlreadyExists => {
- // If the permissions are wrong, delete the directory and recreate it
+ // Recreate the directory if the ownership and permissions are unexpected
if !dir_is_root_owned(&dir, fs_perms.as_ref())? {
+ log::debug!(
+ "Removing old directory due to unexpected permissions: {}",
+ dir.display()
+ );
+
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) {
+ // If the path is not a directory, try to remove the file
+ if err.kind() == io::ErrorKind::NotADirectory {
fs::remove_file(&dir)
} else {
Err(err)
@@ -60,14 +65,36 @@ pub fn create_and_return(dir: PathBuf, permissions: Permissions) -> Result<PathB
}
}
-/// Return whether the directofy is owned by root and, optionally, has the given permissions set
+/// Return whether the directory is owned by root and, optionally, is no less strict
+/// than the desired permissions
fn dir_is_root_owned(dir: &Path, perms: Option<&fs::Permissions>) -> Result<bool> {
- const RELEVANT_BITS: u32 = 0o777;
-
- let meta = fs::symlink_metadata(&dir)
+ 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))
+ .map(|perms| has_at_most_mask(meta.permissions().mode(), perms.mode()))
.unwrap_or(true);
Ok(matching_perms && meta.uid() == 0)
}
+
+/// Return whether `mask` is *at least* as strict as `at_most`
+/// This only considers the read, write, and exec bits.
+fn has_at_most_mask(mask: u32, at_most: u32) -> bool {
+ // Ignore "D" bit, setuid bit, etc.
+ const RELEVANT_BITS: u32 = 0o777;
+ ((mask & RELEVANT_BITS) & !(at_most & RELEVANT_BITS)) == 0
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_has_at_most_mask() {
+ assert!(!has_at_most_mask(0o777, 0o577));
+ assert!(!has_at_most_mask(0o777, 0o707));
+ assert!(!has_at_most_mask(0o777, 0o770));
+
+ assert!(has_at_most_mask(0o777, 0o777));
+ assert!(has_at_most_mask(0o000, 0o777));
+ }
+}