Bugtraq mailing list archives

Re: [linux-security] Things NOT to put in root's crontab


From: szabo_p () maths su oz au (Paul Szabo)
Date: Mon, 27 May 1996 11:47:00 +1000


(This discussion is relevant to many UNIXes besides linux.)

Perhaps a "{}" on the command line should be sub'ed with the relative
name and a "{{}}" should be sub'ed with the absolute name.
I agree that find's syntax would have to be (and should) be extended.

I do not think find should be changed, but the rm command should be replaced
with something more suitable. The following perl script may be useful.

Paul Szabo - System Manager   //        School of Mathematics and Statistics
psz () maths usyd edu au         //   University of Sydney, NSW 2006, Australia

-----
#! /usr/local/bin/perl --
#
#V  safe-rm V1.0 27 May 96  Paul Szabo <psz () maths usyd edu au>
#
#   Safe rm program to be used in root cron jobs like
#     find /tmp -type f -atime +2 -exec safe-rm {} \;
#   instead of rm, to ensure that the path does not contain any symlinks.
#
#   # There is a race between when find starts to descend into /tmp and when it
#   # calls rm. Suppose I make deeply nested  trees like
#   #
#   #   /tmp/a/a/a/a/a/a/passwd         (all real dirs and file) and also
#   #   /tmp/b/a/a/a/a/a -> /etc        (all real dirs and the last symlink)
#   #
#   # then, after find starts up but before it reaches /tmp/a/.../passwd I do
#   #
#   #   cd /tmp; mv a c; mv b a
#   #
#   # then find will exec 'rm /tmp/a/a/.../a/passwd' but this removes /etc/passwd.
#   # If the directories are deep enough then find will slow down a lot, and the
#   # race will be easy to win.
#
#   If using safe-rm then we can also try to remove empty directories:
#     find /tmp -atime +2 -exec safe-rm {} \;


if ( -d '/usr/apollo' ) { $apollo = 1; }


( $CMD = $0 ) =~ s!^/?([^/]*/)*!!;


sub err {
  if ("$USAGE" ne '') {
    if ($#_ >= 0) { print "$CMD failed with error:\n\n"; }
    else          { print "$CMD failed with some unknown error.\n"; }
  }
  foreach (@_) { print "$_\n"; }
  if ("$USAGE" ne '') { print "\nUsage:$USAGE"; }
  exit 1;
}


# Returns success or failure whether path given is acceptable
sub goodpath {
  my ($path) = @_;
  if ( length($path) < 1 || length($path) > 999 ) { return 0; }
  if ( $path =~ m![^a-zA-Z0-9/.,:_-]! ) { return 0; }
  if ( $path =~ m!^[^a-zA-Z0-9/.]! ) { return 0; }
  if ( $apollo ) { if ( $path =~ m!/[^a-zA-Z0-9/.]! ) { return 0; }; if ( $path =~ m!.//! ) { return 0; } }
  else           { if ( $path =~ m!/[^a-zA-Z0-9.]! ) { return 0; } }
  if ( $path =~ m![^/]/$! ) { return 0; }
  return 1;
}


# Returns full (absolute) path beginning with /, or error message.
# Could be simplified for safe-rm: only need to ensure that we got
# a path starting with / and check for symlinks.
sub fullpath {
  # Whinge: Why is this not part of standard Perl?
  # Or at least why is getwd not implemented?

  my ($path) = @_;
  my ($obj, $dir, $nam, $top, $loop, @statp, @statt, @statd, @stato);

  if ( $apollo ) { $top = '//'; }
  else           { $top = '/'; }

  goodpath($path) || return "Bad pathname $path .";
  @statp = stat("$path"); $#statp = 1;
  if ( ! -e _ ) { return "Object $path does not exist"; }

  $obj = "$path";

  if ( $obj =~ m![^/]/$! ) { $obj =~ s!/$!!; }
  ( $dir = "$obj" ) =~ s![^/]*$!!;
  ( $nam = "$obj" ) =~ s!^.*/!!;
  if ( "$obj" ne "$dir$nam" ) { return "Cannot decompose object name $obj: $dir and $nam ?"; }

  lstat("$obj");

  $loop = 0;
  while ( -l _ ) {
    $loop++;
    if ( $loop > 20 ) { return "Symlink loop in $obj"; }
    $nam = readlink("$obj");
    if ("$nam" eq '') { return "Cannot resolve link $obj: $!"; }
    $obj = "$dir$nam";
    goodpath($obj) || return "Bad object name $obj .";
    ( $dir = "$obj" ) =~ s![^/]*$!!;
    ( $nam = "$obj" ) =~ s!^.*/!!;
    if ( "$obj" ne "$dir$nam" ) { return "Cannot decompose object name $obj: $dir and $nam ?"; }

    @stato = stat("$obj"); $#stato = 1;
    if ( "@statp" ne "@stato" ) { return "Cannot resolve $path: not same as $obj ?"; }
    lstat("$obj");
  }

  if ( "$nam" eq '.' || "$nam" eq '..' ) { $dir = "$dir$nam"; $nam = ''; }

  @statt = stat("$top"); $#statt = 1;
  if ( ! -d _ ) { return "But $top is not a directory ?"; }

  if ("$dir" eq '') { $dir = '.'; }
  if ( $dir =~ m![^/]/$! ) { $dir =~ s!/$!!; }

  @statd = stat("$dir"); $#statd = 1;

  $loop = 0;
  while ( "@statd" ne "@statt" ) {
    if ( $loop > 100 ) { return "Directory loop in $obj"; }
    if ( ! -d _ ) { return "But $dir is not a directory ?"; }
    opendir (DH,"$dir/..") || return "Cannot read directory $dir/.. ?";
    @stato = ();
    while ( "@statd" ne "@stato" ) {
      $name = readdir(DH) || last;
      goodpath("$dir/../$name") || next;
      @stato = lstat("$dir/../$name"); $#stato = 1;
    }
    if ( "@statd" ne "@stato" ) { return "Cannot look up $dir (for $dir/$nam) in $dir/.. ?"; }
    closedir (DH) || return "Cannot stop reading directory $dir/.. ?";
    $dir = "$dir/..";
    if ( "$nam" eq '' ) { $nam = "$name"; }
    else                { $nam = "$name/$nam"; }
    goodpath($nam) || return "Bad name $dir/$nam .";
    @statd = stat("$dir"); $#statd = 1;
    if ( "@statd" eq "@stato" ) { last; }
  }

  $obj = "$top$nam";
  goodpath($obj) || return "Bad final pathname $obj";

  @stato = stat("$obj"); $#stato = 1;
  if ( "@statp" ne "@stato" ) { return "Cannot resolve $path: not same as $obj ?"; }

  return "$obj";
}



if ( $#ARGV != 0 ) { err ("Specify one object (only) to remove."); }
($FILE) = @ARGV;

# These checks are somewhat redundant
goodpath($FILE) || err ("Bad object name $FILE .");
@STATFILE = lstat("$FILE");
if ( ! -e _ ) { err ("File $FILE does not exist."); }
if (   -l _ ) { err ("Object $FILE is a symbolic link."); }
if ( ! -d _ && ! -f _ ) { err ("Object $FILE is not a (plain) file or a directory."); }

$FULLPATH = fullpath("$FILE");
if ( $FULLPATH !~ m!^/! ) { err ("Error resolving $FILE:", "  $FULLPATH"); }
if ( "$FULLPATH" ne "$FILE" ) { err ("Not full pathname $FILE :", "  it really is $FULLPATH"); }

# Some more redundancy
@STATFULL = lstat("$FILE");
if ("@STATFILE" ne "@STATFULL") { err ("Error resolving $FILE : seems to have changed."); }

if ( -f _ ) {
  # print "About to unlink $FILE ...\n";
  unlink "$FILE" || err ("Cannot remove file $FILE");
}
elsif ( -d _ ) {
  # print "About to rmdir $FILE ...\n";
  rmdir "$FILE"; # || err ("Cannot remove dir $FILE");
  # No error message: it may have been not empty
}
else { err ("Object $FILE is not a (plain) file nor a directory."); }

#!#



Current thread: