Fix: Mount-Verzeichnisse bekommen explizite Rechte statt sich auf Umask zu verlassen

mount_base_dir (von allen Paaren gemeinsam genutzt) sowie die versteckten
Backing-Verzeichnisse eines Paares wurden bisher nur per create_dir_all()
angelegt, ohne eigenes chmod/chown - die tatsaechlichen Zugriffsrechte hingen
also allein vom Umask des root-Prozesses ab statt von einer bewussten
Entscheidung.

- activate_symlink() setzt den gemeinsamen Elternordner (mount_base_dir) jetzt
  hart auf 0755, unabhaengig vom Umask.
- reconcile::mount_side() chownt das jeweilige Backing-Verzeichnis eines
  Paares mit gesetztem owner_user auf diesen Nutzer und setzt 0700 (neue
  Funktion target::ensure_owner_only_dir) - insbesondere fuer NFS relevant,
  wo es (anders als CIFS/WebDAV) keine uid=/gid=-Mount-Option gibt, die den
  Zugriff nach dem Mounten sonst regeln wuerde.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-20 13:34:06 +02:00
co-authored by Claude Sonnet 5
parent f0c0a69b9c
commit bd319523a7
3 changed files with 75 additions and 4 deletions
+9 -4
View File
@@ -105,9 +105,12 @@ Jedes Laufwerkspaar bekommt sein **eigenes** Unterverzeichnis unter `settings.mo
root, siehe oben) - `/run/media` ist auf den meisten Systemen bereits die übliche Konvention
für eingebundene Wechseldatenträger/Netzlaufwerke (z. B. udisks2/GNOME) und liegt auf `tmpfs`,
muss also nie persistieren. Der Mountpoint selbst (und alle nötigen Elternverzeichnisse) werden
bei jedem `mount`/`watch`-Lauf automatisch angelegt, falls sie fehlen. Ist für ein Paar
`owner_user` gesetzt, bekommt genau dieser Nutzer über die Mount-Optionen (`uid=`/`gid=`, siehe
"Architekturentscheidungen" unten) vollen Zugriff auf den Inhalt - die Trennung passiert also
bei jedem `mount`/`watch`-Lauf automatisch angelegt, falls sie fehlen - `mount_base_dir` selbst
fest mit `0755` (durchquerbar für jeden lokalen Nutzer, unabhängig vom Umask des root-Prozesses),
das jeweilige Backing-Verzeichnis (siehe unten) bei gesetztem `owner_user` zusätzlich fest
`chown`t und `0700` (nur der Owner). Ist für ein Paar `owner_user` gesetzt, bekommt genau dieser
Nutzer bei CIFS/WebDAV zusätzlich über die Mount-Optionen (`uid=`/`gid=`, siehe
"Architekturentscheidungen" unten) vollen Zugriff auf den *Inhalt* - die Trennung passiert also
über Zugriffsrechte, nicht über getrennte Mountpoint-Namensräume pro Nutzer.
Der Standard lässt sich in `config.toml` unter `[settings] mount_base_dir = "..."` jederzeit
@@ -207,7 +210,9 @@ Eintragen ausgegeben:
vollen Zugriff darauf, sofern nicht per `--owner-user` (nicht-interaktiv) oder im
interaktiven Prompt bewusst ein anderer Nutzer angegeben bzw. abgelehnt wird. Bei NFS gibt es
keine clientseitige `uid=`/`gid=`-Option, Zugriff bestimmt dort ausschließlich der Server über
die tatsächlichen Datei-Eigentümer/-Rechte des Exports.
die tatsächlichen Datei-Eigentümer/-Rechte des Exports - `owner_user` wirkt sich hier nur auf
das lokale Backing-Verzeichnis selbst aus (`chown`+`0700`, s. o.), nicht auf das, was der
Server tatsächlich exportiert.
- **davfs2-Konfiguration**: SmartMount setzt in `davfs2.conf` automatisch `gui_optimize 1`
(bündelt PROPFIND-Anfragen, wichtig für grafische Dateimanager) sowie `buf_size 16384`
(deutlich über dem Standard von 16 KiB) - Letzteres behebt ein bekanntes Praxisproblem, bei
+60
View File
@@ -15,6 +15,7 @@
//! kurz während eines Umschaltens (siehe [`crate::reconcile`]) mehr als ein Backing-Verzeichnis
//! gemountet - im Ruhezustand immer nur eines, wie ursprünglich vorgesehen.
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use crate::config::{CloudSide, DrivePair, GlobalSettings, LocalSide, MountKind};
@@ -159,6 +160,21 @@ fn resolve_uid_gid(username: &str) -> Result<(u32, u32)> {
Ok((run_id(username, "-u")?, run_id(username, "-g")?))
}
/// Macht `owner` (Nutzer + dessen primäre Gruppe) zum alleinigen Besitzer von `dir` (`0700`).
/// Für das Backing-Verzeichnis eines Paares mit gesetztem `owner_user` - unabhängig vom
/// Mount-Typ, also auch für NFS, wo es (anders als bei CIFS/WebDAV) keine `uid=`/`gid=`-
/// Mount-Option gibt, die die nach dem Mounten sichtbaren Rechte übersteuern könnte: das
/// Verzeichnis selbst gehört so wenigstens `owner` - reicht z. B. aus, um es zu betreten, bevor/
/// falls etwas gemountet ist, ersetzt aber nicht die serverseitige NFS-Export-Rechtevergabe
/// (siehe README, Abschnitt "Voller Zugriff für einen bestimmten Nutzer").
pub(crate) fn ensure_owner_only_dir(dir: &Path, owner: &str) -> Result<()> {
let (uid, gid) = resolve_uid_gid(owner)?;
std::os::unix::fs::chown(dir, Some(uid), Some(gid)).map_err(|e| Error::io(dir, e))?;
std::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o700))
.map_err(|e| Error::io(dir, e))?;
Ok(())
}
fn run_id(username: &str, flag: &str) -> Result<u32> {
let output = std::process::Command::new("id")
.arg(flag)
@@ -263,6 +279,13 @@ pub fn activate_symlink(pair: &DrivePair, side: Side) -> Result<()> {
if let Some(parent) = link_path.parent() {
std::fs::create_dir_all(parent).map_err(|e| Error::io(parent, e))?;
// `mount_base_dir` ist von allen Paaren (mit ggf. unterschiedlichem `owner_user`)
// gemeinsam genutzt - hart auf `0755` setzen statt sich auf das Umask des root-Prozesses
// zu verlassen, damit jeder lokale Nutzer bis zum eigentlichen Mountpoint durchqueren
// kann (die eigentliche Zugriffsbeschränkung passiert weiter unten je Paar, siehe
// `ensure_owner_only_dir`/die `uid=`/`gid=`-Mount-Optionen).
std::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o755))
.map_err(|e| Error::io(parent, e))?;
}
let target = backing_dir(pair, side);
@@ -466,4 +489,41 @@ mod tests {
let err = activate_symlink(&pair, Side::Local).unwrap_err();
assert!(err.to_string().contains("real directory"));
}
#[test]
fn ensure_owner_only_dir_sets_mode_0700() {
let user = std::env::var("USER").expect("USER env var set in test environment");
let dir = tempfile::tempdir().expect("tempdir");
ensure_owner_only_dir(dir.path(), &user).expect("ensure_owner_only_dir");
let mode = std::fs::metadata(dir.path())
.expect("metadata")
.permissions()
.mode();
assert_eq!(mode & 0o777, 0o700);
}
#[test]
fn ensure_owner_only_dir_fails_clearly_for_unknown_user() {
let dir = tempfile::tempdir().expect("tempdir");
let err = ensure_owner_only_dir(dir.path(), "no-such-user-xyz").unwrap_err();
assert!(err.to_string().contains("not found"));
}
#[test]
fn activate_symlink_sets_shared_parent_dir_to_0755() {
let dir = tempfile::tempdir().expect("tempdir");
let parent = dir.path().join("mount-base");
let mut pair = sample_pair();
pair.mount_point = parent.join("pair-1");
activate_symlink(&pair, Side::Local).expect("activate local");
let mode = std::fs::metadata(&parent)
.expect("metadata")
.permissions()
.mode();
assert_eq!(mode & 0o777, 0o755);
}
}
+6
View File
@@ -282,6 +282,12 @@ async fn mount_side(
target::build_target_with_cached_local_ip(pair, settings, side, cached_local_ip)?;
std::fs::create_dir_all(&mount_target.mount_point)
.map_err(|e| crate::error::Error::io(&mount_target.mount_point, e))?;
// Backing-Verzeichnis auf `owner_user` chownen (0700) - relevant vor allem für NFS, wo es
// (anders als CIFS/WebDAV) keine `uid=`/`gid=`-Mount-Option gibt, die den Zugriff nach dem
// Mounten sonst regeln würde (siehe `target::ensure_owner_only_dir`).
if let Some(owner) = &pair.owner_user {
target::ensure_owner_only_dir(&mount_target.mount_point, owner)?;
}
let backend = mount::backend_for(target::side_kind(pair, side));
backend.check_available()?;