Fix: cifs-Credentials-Datei lehnt Newlines und leeres Passwort ab
write_credentials_file() schrieb username/password/domain ungeprüft in die zeilenbasierte (key=value) Credentials-Datei für mount.cifs - ein eingebettetes Newline in einem Feld hätte eine zusätzliche, nicht vorgesehene Zeile einschleusen können (z. B. ein überraschendes domain= oder ein zweites password=, das das eigentliche überschreibt). Felder mit Newline/Carriage-Return sowie ein leeres Passwort werden jetzt vor dem Schreiben abgelehnt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LjyzpGWECyKSBWz5DtkTGz
This commit is contained in:
@@ -75,7 +75,33 @@ pub fn credentials_path(pair_id: &str, side: crate::db::credentials::Side) -> Pa
|
||||
base.join(format!("{pair_id}-{}.cred", side.as_str()))
|
||||
}
|
||||
|
||||
/// `mount.cifs`s Credentials-Datei ist zeilenbasiert (`key=value`) - ein eingebettetes
|
||||
/// Newline in einem Feld würde eine zusätzliche, nicht vorgesehene `key=value`-Zeile
|
||||
/// einschleusen (z. B. ein überraschendes `domain=...` oder ein zweites `password=`, das das
|
||||
/// eigentliche überschreibt). Ein leeres Passwort ist ebenfalls kein sinnvoller Wert.
|
||||
fn validate_credential_field(name: &str, value: &str) -> Result<()> {
|
||||
if value.contains(['\n', '\r']) {
|
||||
return Err(Error::Other(format!(
|
||||
"cifs credential field '{name}' must not contain a newline"
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_credentials_file(path: &PathBuf, cred: &Credential) -> Result<()> {
|
||||
if let Some(username) = &cred.username {
|
||||
validate_credential_field("username", username)?;
|
||||
}
|
||||
validate_credential_field("password", &cred.password)?;
|
||||
if cred.password.is_empty() {
|
||||
return Err(Error::Other(
|
||||
"cifs credential has an empty password".to_string(),
|
||||
));
|
||||
}
|
||||
if let Some(domain) = &cred.domain {
|
||||
validate_credential_field("domain", domain)?;
|
||||
}
|
||||
|
||||
if let Some(dir) = path.parent() {
|
||||
std::fs::create_dir_all(dir).map_err(|e| Error::io(dir, e))?;
|
||||
}
|
||||
@@ -138,6 +164,33 @@ mod tests {
|
||||
assert!(contents.contains("domain=WORKGROUP"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_password_with_embedded_newline() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let path = dir.path().join("test.cred");
|
||||
let cred = Credential {
|
||||
username: Some("nasuser".to_string()),
|
||||
domain: None,
|
||||
password: "s3cret\ndomain=INJECTED".to_string(),
|
||||
};
|
||||
|
||||
assert!(write_credentials_file(&path, &cred).is_err());
|
||||
assert!(!path.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_empty_password() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let path = dir.path().join("test.cred");
|
||||
let cred = Credential {
|
||||
username: Some("nasuser".to_string()),
|
||||
domain: None,
|
||||
password: String::new(),
|
||||
};
|
||||
|
||||
assert!(write_credentials_file(&path, &cred).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn credentials_path_differs_per_side() {
|
||||
let local = credentials_path("pair-1", Side::Local);
|
||||
|
||||
Reference in New Issue
Block a user