From d0e5af23217ed671eeb8db586f12d6dc6cd79b75 Mon Sep 17 00:00:00 2001 From: Jacek Kurlit Date: Sun, 21 Dec 2025 20:22:39 +0100 Subject: [PATCH 1/5] rm: fix for -rf ./ and variants silently delete current directory contents --- src/uu/rm/src/rm.rs | 2 ++ tests/by-util/test_rm.rs | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index a20a57d7f36..0443bc51f62 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -851,7 +851,9 @@ fn path_is_current_or_parent_directory(path: &Path) -> bool { let dir_separator = MAIN_SEPARATOR as u8; if let Ok(path_bytes) = path_str { return path_bytes == ([b'.']) + || path_bytes == ([b'.', dir_separator]) || path_bytes == ([b'.', b'.']) + || path_bytes == ([b'.', b'.', dir_separator]) || path_bytes.ends_with(&[dir_separator, b'.']) || path_bytes.ends_with(&[dir_separator, b'.', b'.']) || path_bytes.ends_with(&[dir_separator, b'.', dir_separator]) diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index 38230f2ad36..6cb0d4e0588 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -773,6 +773,10 @@ fn test_current_or_parent_dir_rm4() { "rm: refusing to remove '.' or '..' directory: skipping 'd/./'", "rm: refusing to remove '.' or '..' directory: skipping 'd/..'", "rm: refusing to remove '.' or '..' directory: skipping 'd/../'", + "rm: refusing to remove '.' or '..' directory: skipping '.'", + "rm: refusing to remove '.' or '..' directory: skipping './'", + "rm: refusing to remove '.' or '..' directory: skipping '../'", + "rm: refusing to remove '.' or '..' directory: skipping '..'", ]; let std_err_str = ts .ucmd() @@ -782,6 +786,10 @@ fn test_current_or_parent_dir_rm4() { .arg("d/.////") .arg("d/..") .arg("d/../") + .arg(".") + .arg("./") + .arg("../") + .arg("..") .fails() .stderr_move_str(); @@ -804,6 +812,10 @@ fn test_current_or_parent_dir_rm4_windows() { "rm: refusing to remove '.' or '..' directory: skipping 'd\\.\\'", "rm: refusing to remove '.' or '..' directory: skipping 'd\\..'", "rm: refusing to remove '.' or '..' directory: skipping 'd\\..\\'", + "rm: refusing to remove '.' or '..' directory: skipping '.'", + "rm: refusing to remove '.' or '..' directory: skipping '.\\'", + "rm: refusing to remove '.' or '..' directory: skipping '..'", + "rm: refusing to remove '.' or '..' directory: skipping '..\\'", ]; let std_err_str = ts .ucmd() @@ -813,6 +825,10 @@ fn test_current_or_parent_dir_rm4_windows() { .arg("d\\.\\\\\\\\") .arg("d\\..") .arg("d\\..\\") + .arg(".") + .arg(".\\") + .arg("..") + .arg("..\\") .fails() .stderr_move_str(); From 5eab20cf823112d3d82668634200900d678d7096 Mon Sep 17 00:00:00 2001 From: Jacek Kurlit Date: Sun, 21 Dec 2025 22:06:24 +0100 Subject: [PATCH 2/5] tests/rm: added checks for silent removal --- tests/by-util/test_rm.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index 6cb0d4e0588..2e1ef78fc84 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -767,6 +767,12 @@ fn test_current_or_parent_dir_rm4() { at.mkdir("d"); + let file_1 = "file1"; + let file_2 = "d/file2"; + + at.touch(file_1); + at.touch(file_2); + let answers = [ "rm: refusing to remove '.' or '..' directory: skipping 'd/.'", "rm: refusing to remove '.' or '..' directory: skipping 'd/./'", @@ -796,6 +802,10 @@ fn test_current_or_parent_dir_rm4() { for (idx, line) in std_err_str.lines().enumerate() { assert_eq!(line, answers[idx]); } + // checks that no file was silently removed + assert!(at.dir_exists("d")); + assert!(at.file_exists(file_1)); + assert!(at.file_exists(file_2)); } #[test] From 59509e2ad9568919b04c49165529535c16a9bfa5 Mon Sep 17 00:00:00 2001 From: Jacek Kurlit Date: Sun, 21 Dec 2025 22:11:11 +0100 Subject: [PATCH 3/5] tests/rm: added silent removal checks for windows --- tests/by-util/test_rm.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index 2e1ef78fc84..ad52fc35a7f 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -816,6 +816,12 @@ fn test_current_or_parent_dir_rm4_windows() { at.mkdir("d"); + let file_1 = "file1"; + let file_2 = "d/file2"; + + at.touch(file_1); + at.touch(file_2); + let answers = [ "rm: refusing to remove '.' or '..' directory: skipping 'd\\.'", "rm: refusing to remove '.' or '..' directory: skipping 'd\\.\\'", @@ -845,6 +851,11 @@ fn test_current_or_parent_dir_rm4_windows() { for (idx, line) in std_err_str.lines().enumerate() { assert_eq!(line, answers[idx]); } + + // checks that no file was silently removed + assert!(at.dir_exists("d")); + assert!(at.file_exists(file_1)); + assert!(at.file_exists(file_2)); } #[test] From 8077ca27d1e2ceb3b9fe604bab9c2bc5f1dabb48 Mon Sep 17 00:00:00 2001 From: RustyJack Date: Mon, 22 Dec 2025 09:31:27 +0100 Subject: [PATCH 4/5] uucore: add missing test cleanup --- src/uucore/src/lib/features/backup_control.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uucore/src/lib/features/backup_control.rs b/src/uucore/src/lib/features/backup_control.rs index ed6b6703441..87a2e0f80b9 100644 --- a/src/uucore/src/lib/features/backup_control.rs +++ b/src/uucore/src/lib/features/backup_control.rs @@ -682,6 +682,7 @@ mod tests { let result = determine_backup_mode(&matches).unwrap(); assert_eq!(result, BackupMode::Numbered); + unsafe { env::remove_var(ENV_VERSION_CONTROL) }; } #[test] From 745e5f6dce3c0e85f25f0a4a8155d5599d14245c Mon Sep 17 00:00:00 2001 From: RustyJack Date: Mon, 22 Dec 2025 10:43:20 +0100 Subject: [PATCH 5/5] uucore: added commentary for test cleanup reason --- src/uucore/src/lib/features/backup_control.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/uucore/src/lib/features/backup_control.rs b/src/uucore/src/lib/features/backup_control.rs index 87a2e0f80b9..bf13b12988c 100644 --- a/src/uucore/src/lib/features/backup_control.rs +++ b/src/uucore/src/lib/features/backup_control.rs @@ -602,6 +602,7 @@ mod tests { let result = determine_backup_mode(&matches).unwrap(); assert_eq!(result, BackupMode::Numbered); + // cleaning up because this can affect tests that rely on env state unsafe { env::remove_var(ENV_VERSION_CONTROL) }; } @@ -615,6 +616,7 @@ mod tests { let result = determine_backup_mode(&matches).unwrap(); assert_eq!(result, BackupMode::None); + // cleaning up because this can affect tests that rely on env state unsafe { env::remove_var(ENV_VERSION_CONTROL) }; } @@ -630,6 +632,7 @@ mod tests { assert!(result.is_err()); let text = format!("{}", result.unwrap_err()); assert!(text.contains("invalid argument 'foobar' for '$VERSION_CONTROL'")); + // cleaning up because this can affect tests that rely on env state unsafe { env::remove_var(ENV_VERSION_CONTROL) }; } @@ -645,6 +648,7 @@ mod tests { assert!(result.is_err()); let text = format!("{}", result.unwrap_err()); assert!(text.contains("ambiguous argument 'n' for '$VERSION_CONTROL'")); + // cleaning up because this can affect tests that rely on env state unsafe { env::remove_var(ENV_VERSION_CONTROL) }; } @@ -658,6 +662,7 @@ mod tests { let result = determine_backup_mode(&matches).unwrap(); assert_eq!(result, BackupMode::Simple); + // cleaning up because this can affect tests that rely on env state unsafe { env::remove_var(ENV_VERSION_CONTROL) }; } @@ -682,6 +687,7 @@ mod tests { let result = determine_backup_mode(&matches).unwrap(); assert_eq!(result, BackupMode::Numbered); + // cleaning up because this can affect tests that rely on env state unsafe { env::remove_var(ENV_VERSION_CONTROL) }; }