#!/usr/bin/perl -T # Perl wrapper around du to only allow du on directories owned by the # user. This prevents du sessions of (for example) a 2 terabyte raid # filesystem, which takes hours to finish. # I started this in bash but switched to perl because you can't do # setgid shell scripts on linux. This script must be installed setgid # root. That should be reasonably secure. # You better set /usr/bin/du to not be executable by other, otherwise # this is pretty pointless. # Phil Hollenback, philiph@pobox.com 9/22/05 use Getopt::Std; use Cwd; use POSIX qw(getuid); # Have to set the path because we are in taint mode. $ENV{"PATH"} = "/bin:/usr/bin"; $ENV{"BASH_ENV"} = ""; # how long do we allow du to run before killing it (in seconds)? $TimeOut=600; $RealDu="/usr/bin/unsafe-du"; -x $RealDu or die "can't execute $RealDu"; # Save the original command-line args. @ORGARGS=@ARGV; # Use getopt just to remove all options (so we are left with just path # arguments). getopt ":"; # Untaint the arguments. foreach (@ORGARGS) { ($arg) = ($_ =~ /(.*)/); push @NEWARGS,$arg; } # Determine if user is in wheel group. $uid = getuid; $username = getpwuid($uid); # get the members of group wheel... ($name,$passwd,$gid,$members) = getgrnam("wheel"); # now see if our username is in the members list or our uid is 0. if (( $members =~ m|$username|) || ($uid == 0)) { # run the real du no questions asked. exec("$RealDu @NEWARGS"); ### NOTREACHED ### } # Now @ARGV contains _only_ non-option arguments. @PathList=@ARGV; # Special case: if there aren't any arguments left, that means the # user ran du without a path specification. Thus du should be run on # the current directory. Push the current dir into the list to force # this. if ( $#PathList == -1 ) { push @PathList, getcwd; } # go through each remaining command-line argument (path) and see if it # is owned by the user. foreach (@PathList) { -O $_ or die "you don't own $_"; } # Set a timer and run the real du. eval { local $SIG{ALRM} = sub { # ignore SIGHUP here so the kill only affects children. local $SIG{HUP} = 'IGNORE'; kill 1,(-$$); print STDERR "du terminated, max run time of $TimeOut seconds exceeded.\n"; }; alarm $TimeOut; system ("$RealDu @NEWARGS") || die "failed to run $RealDu: $!"; alarm 0; }; $SIG{HUP} = 'DEFAULT'; exit 0;