Hi.

The script rmbug (attached) will demonstrate how "rm -rf" segfaults with
deep directories. I believe this is because it runs out of stack when the
recursive routines are called enough times. With the default (on my
system, anyway) of 8192k stack, about 13500 levels deep is necessary to
make rm segfault. On some filesystems, like reiserfs, it takes only a few
seconds to create a hierarchy this deep.

This could be abused maliciously, for instance to make /tmp run full when
scripts to delete old files don't work, which in turn could cause all
sorts of weird behaviour. I guess this might qualify as a local DoS. The
solution is to rewrite the code to delete the directories using a
while-loop instead of recursion, something like the perl script rm-rf
(attached). Use with care, it has not been tested much.

regards,
Ketil Froyn
#!/bin/bash
#
# Copyright (C) 2002 by Ketil Froyn <[EMAIL PROTECTED]>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or (at
# your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA
#
#
# Demonstrate segfault in "rm -rf" when the stack runs full
#
ulimit -s 64

if [ -e aaa ]; then
  echo "There is a file or directory already called 'aaa' here. Remove it or";
  echo "run this script from somewhere else";
  exit
fi;

echo Creating directory structure
perl -e 'for ($i=0; $i < 200; $i++) { unless(mkdir("aaa",0777)) { die "Unable to 
create dirs: $!"; } chdir "aaa"; }'

echo -n "Counting directories created...: "
perl -e '$i=0; while (-e "aaa") { unless (chdir "aaa") { die "error: $!"; } $i++; } 
print "$i\n";'

echo "Hit enter to continue"
head -1 > /dev/null

echo Attempting to remove directory structure with rm \(should segfault\)
rm -rf aaa

echo "Hit enter to continue"
head -1 > /dev/null

echo Removing directories \(assuming nothing else has been put in there\)
perl -e '$i=0; while ( -e "aaa") { $i++; chdir "aaa"; if (-e "core") {unlink "core";} 
}; chdir ".."; while ($i--) { unless (rmdir "aaa") { die "Unable to rmdir aaa: $!"; } 
chdir ".."};'
#!/usr/bin/perl
#
# Copyright (C) 2002 by Ketil Froyn <[EMAIL PROTECTED]>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or (at
# your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA
#
#
# Quick'n'dirty script to remove directories recursively
#
use strict;

my $i;
my $cur=`pwd`;
chomp $cur;

for ($i = 0; $i <= $#ARGV; $i++) {

    if (-d $ARGV[$i]) {
        if (!rmdir($ARGV[$i])) {
            chdir($ARGV[$i]);
            rm_rf();
            chdir($cur);
            rmdir($ARGV[$i]);
        }
    } else {
        unlink($ARGV[$i]);
    }
}

sub rm_rf {
    my $d = 0;
    my $f;
    
    while (1) { 
        opendir D, ".";
        my $e;
        $f = ".."; # This is the default dir to move into
        while (($e = readdir(D))) {
            next if ($e eq ".");
            next if ($e eq "..");
            if (-d $e) {
                unless (rmdir($e)) { # Try to rmdir directories when found
                    $f = $e; # If that didn't work, we'll recurse down
                    last;
                }
                print "Remove: $e (dir)\n";
            } else {
                print "Remove: $e (file)\n";
                unlink($e);
            }
        }
        closedir D;

        if ($f eq "..") {
            $d--;
        } else {
            $d++;
        }
        last if ($d < 0);
        chdir $f;       
    }
}

Reply via email to