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()))
|
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<()> {
|
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() {
|
if let Some(dir) = path.parent() {
|
||||||
std::fs::create_dir_all(dir).map_err(|e| Error::io(dir, e))?;
|
std::fs::create_dir_all(dir).map_err(|e| Error::io(dir, e))?;
|
||||||
}
|
}
|
||||||
@@ -138,6 +164,33 @@ mod tests {
|
|||||||
assert!(contents.contains("domain=WORKGROUP"));
|
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]
|
#[test]
|
||||||
fn credentials_path_differs_per_side() {
|
fn credentials_path_differs_per_side() {
|
||||||
let local = credentials_path("pair-1", Side::Local);
|
let local = credentials_path("pair-1", Side::Local);
|
||||||
|
|||||||
Reference in New Issue
Block a user