Skip to content

Commit da314c0

Browse files
committed
rm: on linux use the safe traversal in all cases
1 parent e105eb5 commit da314c0

1 file changed

Lines changed: 53 additions & 57 deletions

File tree

src/uu/rm/src/rm.rs

Lines changed: 53 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ fn is_readable_metadata(metadata: &Metadata) -> bool {
394394
}
395395

396396
/// Whether the given file or directory is readable.
397-
#[cfg(unix)]
397+
#[cfg(all(unix, not(target_os = "linux")))]
398398
fn is_readable(path: &Path) -> bool {
399399
match fs::metadata(path) {
400400
Err(_) => false,
@@ -590,19 +590,14 @@ fn remove_dir_recursive(path: &Path, options: &Options) -> bool {
590590
return false;
591591
}
592592

593-
// Use secure traversal on Linux for long paths
593+
// Use secure traversal on Linux to prevent TOCTOU attacks
594594
#[cfg(target_os = "linux")]
595-
{
596-
if let Some(s) = path.to_str() {
597-
if s.len() > 1000 {
598-
return safe_remove_dir_recursive(path, options);
599-
}
600-
}
601-
}
595+
return safe_remove_dir_recursive(path, options);
602596

603-
// Fallback for non-Linux or shorter paths
597+
// Fallback for non-Linux systems
604598
#[cfg(not(target_os = "linux"))]
605599
{
600+
// Handle very long paths with remove_dir_all
606601
if let Some(s) = path.to_str() {
607602
if s.len() > 1000 {
608603
match fs::remove_dir_all(path) {
@@ -617,62 +612,63 @@ fn remove_dir_recursive(path: &Path, options: &Options) -> bool {
617612
}
618613
}
619614
}
620-
}
621615

622-
// Recursive case: this is a directory.
623-
let mut error = false;
624-
match fs::read_dir(path) {
625-
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
626-
// This is not considered an error.
627-
}
628-
Err(_) => error = true,
629-
Ok(iter) => {
630-
for entry in iter {
631-
match entry {
632-
Err(_) => error = true,
633-
Ok(entry) => {
634-
let child_error = remove_dir_recursive(&entry.path(), options);
635-
error = error || child_error;
616+
// Recursive case: this is a directory.
617+
let mut error = false;
618+
match fs::read_dir(path) {
619+
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
620+
// This is not considered an error.
621+
}
622+
Err(_) => error = true,
623+
Ok(iter) => {
624+
for entry in iter {
625+
match entry {
626+
Err(_) => error = true,
627+
Ok(entry) => {
628+
let child_error = remove_dir_recursive(&entry.path(), options);
629+
error = error || child_error;
630+
}
636631
}
637632
}
638633
}
639634
}
640-
}
641-
642-
// Ask the user whether to remove the current directory.
643-
if options.interactive == InteractiveMode::Always && !prompt_dir(path, options) {
644-
return false;
645-
}
646635

647-
// Try removing the directory itself.
648-
match fs::remove_dir(path) {
649-
Err(_) if !error && !is_readable(path) => {
650-
// For compatibility with GNU test case
651-
// `tests/rm/unread2.sh`, show "Permission denied" in this
652-
// case instead of "Directory not empty".
653-
show_error!("cannot remove {}: Permission denied", path.quote());
654-
error = true;
655-
}
656-
Err(e) if !error => {
657-
let e =
658-
e.map_err_context(|| translate!("rm-error-cannot-remove", "file" => path.quote()));
659-
show_error!("{e}");
660-
error = true;
636+
// Ask the user whether to remove the current directory.
637+
if options.interactive == InteractiveMode::Always && !prompt_dir(path, options) {
638+
return false;
661639
}
662-
Err(_) => {
663-
// If there has already been at least one error when
664-
// trying to remove the children, then there is no need to
665-
// show another error message as we return from each level
666-
// of the recursion.
640+
641+
// Try removing the directory itself.
642+
match fs::remove_dir(path) {
643+
Err(_) if !error && !is_readable(path) => {
644+
// For compatibility with GNU test case
645+
// `tests/rm/unread2.sh`, show "Permission denied" in this
646+
// case instead of "Directory not empty".
647+
show_error!("cannot remove {}: Permission denied", path.quote());
648+
error = true;
649+
}
650+
Err(e) if !error => {
651+
let e = e.map_err_context(
652+
|| translate!("rm-error-cannot-remove", "file" => path.quote()),
653+
);
654+
show_error!("{e}");
655+
error = true;
656+
}
657+
Err(_) => {
658+
// If there has already been at least one error when
659+
// trying to remove the children, then there is no need to
660+
// show another error message as we return from each level
661+
// of the recursion.
662+
}
663+
Ok(_) if options.verbose => println!(
664+
"{}",
665+
translate!("rm-verbose-removed-directory", "file" => normalize(path).quote())
666+
),
667+
Ok(_) => {}
667668
}
668-
Ok(_) if options.verbose => println!(
669-
"{}",
670-
translate!("rm-verbose-removed-directory", "file" => normalize(path).quote())
671-
),
672-
Ok(_) => {}
673-
}
674669

675-
error
670+
error
671+
}
676672
}
677673

678674
fn handle_dir(path: &Path, options: &Options) -> bool {

0 commit comments

Comments
 (0)